]> granicus.if.org Git - yasm/commitdiff
Add support for FAR call/jmp. Because of the jmp label, label equ seg:off
authorPeter Johnson <peter@tortall.net>
Tue, 27 May 2003 04:13:16 +0000 (04:13 -0000)
committerPeter Johnson <peter@tortall.net>
Tue, 27 May 2003 04:13:16 +0000 (04:13 -0000)
problem, adding this required adding some fields to x86_jmprel (now a
misnomer, as FAR jumps are absolute) to save the far opcode, and additional
support in libyasm's yasm_expr_* to properly handle the YASM_EXPR_SEGOFF
operator.

svn path=/trunk/yasm/; revision=954

libyasm/bytecode.h
libyasm/coretype.h
libyasm/expr.c
libyasm/expr.h
modules/arch/x86/x86arch.h
modules/arch/x86/x86bc.c
modules/arch/x86/x86id.re

index c7f75429cb33c7f39a231e35b06cb504296b25bd..607df29e010ba1abc91b224ad0f6dc619a7c88ce 100644 (file)
@@ -2,7 +2,7 @@
  * \file bytecode.h
  * \brief YASM bytecode interface.
  *
- * $IdPath: yasm/libyasm/bytecode.h,v 1.73 2003/05/04 22:15:09 peter Exp $
+ * $IdPath$
  *
  *  Copyright (C) 2001  Peter Johnson
  *
@@ -257,6 +257,8 @@ yasm_bc_resolve_flags yasm_bc_resolve(yasm_bytecode *bc, int save,
  * \return Newly allocated buffer that should be used instead of buf for
  *        reading the byte representation, or NULL if buf was big enough to
  *        hold the entire byte representation.
+ * \caution Essentially destroys contents of bytecode, so it's NOT safe to call
+ *          twice on the same bytecode.
  */
 /*@null@*/ /*@only@*/ unsigned char *yasm_bc_tobytes
     (yasm_bytecode *bc, unsigned char *buf, unsigned long *bufsize,
index d32f78a89207346ef61131d433bd1fa6ca0170af..94a52f4edee85fb64af45330aec4b5e67445a0ca 100644 (file)
@@ -2,7 +2,7 @@
  * \file coretype.h
  * \brief YASM core types and utility functions.
  *
- * $IdPath: yasm/libyasm/coretype.h,v 1.23 2003/03/31 08:22:05 peter Exp $
+ * $IdPath$
  *
  *  Copyright (C) 2001  Peter Johnson
  *
@@ -85,6 +85,7 @@ typedef struct yasm_valparamhead yasm_valparamhead;
 
 /** Expression operators usable in #yasm_expr expressions. */
 typedef enum {
+    YASM_EXPR_IDENT,   /**< No operation, just a value. */
     YASM_EXPR_ADD,     /**< Arithmetic addition (+). */
     YASM_EXPR_SUB,     /**< Arithmetic subtraction (-). */
     YASM_EXPR_MUL,     /**< Arithmetic multiplication (*). */
@@ -108,11 +109,11 @@ typedef enum {
     YASM_EXPR_LE,      /**< Less than or equal to comparison. */
     YASM_EXPR_GE,      /**< Greater than or equal to comparison. */
     YASM_EXPR_NE,      /**< Not equal comparison. */
+    YASM_EXPR_NONNUM,  /**< Start of non-numeric operations (not an op). */
     YASM_EXPR_SEG,     /**< SEG operator (gets segment portion of address). */
     YASM_EXPR_WRT,     /**< WRT operator (gets offset of address relative to
                         *   some other segment). */
-    YASM_EXPR_SEGOFF,  /**< The ':' in segment:offset. */
-    YASM_EXPR_IDENT    /**< No operation, just a value. */
+    YASM_EXPR_SEGOFF   /**< The ':' in segment:offset. */
 } yasm_expr_op;
 
 /** Symbol record visibility.
index 2963c63712537ac31d7c265c5e9f10737088307e..138cc41a253257711bcaec3092d9e1ffcf45e877 100644 (file)
@@ -519,6 +519,11 @@ expr_level_op(/*@returned@*/ /*@only@*/ yasm_expr *e, int fold_const,
        yasm_xfree(e);
        e = sube;
     }
+
+    /* If non-numeric expression, don't fold constants. */
+    if (e->op > YASM_EXPR_NONNUM)
+       fold_const = 0;
+
     level_numterms = e->numterms;
     level_fold_numterms = 0;
     for (i=0; i<e->numterms; i++) {
@@ -880,6 +885,12 @@ yasm_expr_delete(yasm_expr *e)
 }
 /*@=mustfree@*/
 
+int
+yasm_expr_is_op(const yasm_expr *e, yasm_expr_op op)
+{
+    return (e->op == op);
+}
+
 static int
 expr_contains_callback(const yasm_expr__item *ei, void *d)
 {
@@ -1017,6 +1028,35 @@ yasm_expr_extract_symrec(yasm_expr **ep, yasm_calc_bc_dist_func calc_bc_dist)
     return sym;
 }
 
+yasm_expr *
+yasm_expr_extract_segment(yasm_expr **ep)
+{
+    yasm_expr *retval;
+    yasm_expr *e = *ep;
+
+    /* If not SEG:OFF, we can't do this transformation */
+    if (e->op != YASM_EXPR_SEGOFF)
+       return NULL;
+
+    /* Extract the SEG portion out to its own expression */
+    if (e->terms[0].type == YASM_EXPR_EXPR)
+       retval = e->terms[0].data.expn;
+    else {
+       /* Need to build IDENT expression to hold non-expression contents */
+       retval = yasm_xmalloc(sizeof(yasm_expr));
+       retval->op = YASM_EXPR_IDENT;
+       retval->numterms = 1;
+       retval->terms[0] = e->terms[0]; /* structure copy */
+    }
+
+    /* Delete the SEG: portion by changing the expression into an IDENT */
+    e->op = YASM_EXPR_IDENT;
+    e->numterms = 1;
+    e->terms[0] = e->terms[1]; /* structure copy */
+
+    return retval;
+}
+
 /*@-unqualifiedtrans -nullderef -nullstate -onlytrans@*/
 const yasm_intnum *
 yasm_expr_get_intnum(yasm_expr **ep, yasm_calc_bc_dist_func calc_bc_dist)
@@ -1168,6 +1208,9 @@ yasm_expr_print(FILE *f, const yasm_expr *e)
        case YASM_EXPR_IDENT:
            opstr[0] = 0;
            break;
+       default:
+           strcpy(opstr, " !UNK! ");
+           break;
     }
     for (i=0; i<e->numterms; i++) {
        switch (e->terms[i].type) {
index b0c639a1b8d33ed67513334d37c49caf94139289..786a2556f62adc0c131f8e66372f82f27181dc76 100644 (file)
@@ -2,7 +2,7 @@
  * \file expr.h
  * \brief YASM expression interface
  *
- * $IdPath: yasm/libyasm/expr.h,v 1.40 2003/05/04 20:28:28 peter Exp $
+ * $IdPath$
  *
  *  Copyright (C) 2001  Michael Urman, Peter Johnson
  *
@@ -117,6 +117,14 @@ yasm_expr *yasm_expr_copy(const yasm_expr *e);
  */
 void yasm_expr_delete(/*@only@*/ /*@null@*/ yasm_expr *e);
 
+/** Determine if an expression is a specified operation (at the top level).
+ * \param e            expression
+ * \param op           operator
+ * \return Nonzero if the expression was the specified operation at the top
+ *         level, zero otherwise.
+ */
+int yasm_expr_is_op(const yasm_expr *e, yasm_expr_op op);
+
 /** Extra transformation function for yasm_expr__level_tree().
  * \param e    expression being simplified
  * \param d    data provided as expr_xform_extra_data to
@@ -172,6 +180,15 @@ SLIST_HEAD(yasm__exprhead, yasm__exprentry);
 /*@dependent@*/ /*@null@*/ yasm_symrec *yasm_expr_extract_symrec
     (yasm_expr **ep, yasm_calc_bc_dist_func calc_bc_dist);
 
+/** Extract the segment portion of a SEG:OFF expression, leaving the offset.
+ * \param ep           expression (pointer to)
+ * \return NULL if unable to extract a segment (YASM_EXPR_SEGOFF not the
+ *         top-level operator), otherwise the segment expression.  The input
+ *         expression is modified such that on return, it's the offset
+ *         expression.
+ */
+/*@only@*/ /*@null@*/ yasm_expr *yasm_expr_extract_segment(yasm_expr **e);
+
 /** Get the integer value of an expression if it's just an integer.
  * \param ep           expression (pointer to)
  * \param calc_bc_dist bytecode distance-calculation function
index d96dbef6c80e8a706a671c94f99197b9b3c946b0..99a1694b8ec73edb1d086cf721a359ad595230af 100644 (file)
@@ -69,7 +69,8 @@ typedef enum {
     JR_SHORT,
     JR_NEAR,
     JR_SHORT_FORCED,
-    JR_NEAR_FORCED
+    JR_NEAR_FORCED,
+    JR_FAR                 /* not really relative, but fits here */
 } x86_jmprel_opcode_sel;
 
 typedef enum {
@@ -135,11 +136,14 @@ yasm_bytecode *yasm_x86__bc_new_insn(x86_new_insn_data *d);
 typedef struct x86_new_jmprel_data {
     unsigned long lindex;
     /*@keep@*/ yasm_expr *target;
+    /*@dependent@*/ yasm_symrec *origin;
     x86_jmprel_opcode_sel op_sel;
     unsigned char short_op_len;
     unsigned char short_op[3];
     unsigned char near_op_len;
     unsigned char near_op[3];
+    unsigned char far_op_len;
+    unsigned char far_op[3];
     unsigned char addrsize;
     unsigned char opersize;
 } x86_new_jmprel_data;
index a40312bafaf7138fd89ba7bfa462cbbbf283f6df..2c5cfc94e69908bd65c56a801239eb877124218d 100644 (file)
@@ -25,7 +25,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 #include <util.h>
-/*@unused@*/ RCSID("$IdPath: yasm/modules/arch/x86/x86bc.c,v 1.52 2003/03/26 05:07:55 peter Exp $");
+/*@unused@*/ RCSID("$IdPath$");
 
 #define YASM_LIB_INTERNAL
 #define YASM_BC_INTERNAL
@@ -98,11 +98,12 @@ typedef struct x86_jmprel {
     yasm_bytecode bc;          /* base structure */
 
     yasm_expr *target;         /* target location */
+    /*@dependent@*/ yasm_symrec *origin;    /* jump origin */
 
     struct {
        unsigned char opcode[3];
        unsigned char opcode_len;   /* 0 = no opc for this version */
-    } shortop, nearop;
+    } shortop, nearop, farop;
 
     /* which opcode are we using? */
     /* The *FORCED forms are specified in the source as such */
@@ -192,6 +193,7 @@ yasm_x86__bc_new_jmprel(x86_new_jmprel_data *d)
                           sizeof(x86_jmprel), d->lindex);
 
     jmprel->target = d->target;
+    jmprel->origin = d->origin;
     jmprel->op_sel = d->op_sel;
 
     if ((d->op_sel == JR_SHORT_FORCED) && (d->near_op_len == 0))
@@ -211,6 +213,11 @@ yasm_x86__bc_new_jmprel(x86_new_jmprel_data *d)
     jmprel->nearop.opcode[2] = d->near_op[2];
     jmprel->nearop.opcode_len = d->near_op_len;
 
+    jmprel->farop.opcode[0] = d->far_op[0];
+    jmprel->farop.opcode[1] = d->far_op[1];
+    jmprel->farop.opcode[2] = d->far_op[2];
+    jmprel->farop.opcode_len = d->far_op_len;
+
     jmprel->addrsize = d->addrsize;
     jmprel->opersize = d->opersize;
     jmprel->lockrep_pre = 0;
@@ -539,6 +546,9 @@ yasm_x86__bc_print(FILE *f, int indent_level, const yasm_bytecode *bc)
                case JR_NEAR_FORCED:
                    fprintf(f, "Forced Near");
                    break;
+               case JR_FAR:
+                   fprintf(f, "Far");
+                   break;
                default:
                    fprintf(f, "UNKNOWN!!");
                    break;
@@ -677,7 +687,7 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
     /*@dependent@*/ /*@null@*/ const yasm_intnum *num;
     long rel;
     unsigned char opersize;
-    int jrshort = 0;
+    x86_jmprel_opcode_sel jrtype = JR_NONE;
 
     /* As opersize may be 0, figure out its "real" value. */
     opersize = (jmprel->opersize == 0) ? jmprel->mode_bits :
@@ -689,9 +699,11 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
     switch (jmprel->op_sel) {
        case JR_SHORT_FORCED:
            /* 1 byte relative displacement */
-           jrshort = 1;
+           jrtype = JR_SHORT;
            if (save) {
                temp = yasm_expr_copy(jmprel->target);
+               temp = yasm_expr_new(YASM_EXPR_SUB, yasm_expr_expr(temp),
+                                    yasm_expr_sym(jmprel->origin), bc->line);
                num = yasm_expr_get_intnum(&temp, calc_bc_dist);
                if (!num) {
                    yasm__error(bc->line,
@@ -719,7 +731,7 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
            break;
        case JR_NEAR_FORCED:
            /* 2/4 byte relative displacement (depending on operand size) */
-           jrshort = 0;
+           jrtype = JR_NEAR;
            if (save) {
                if (jmprel->nearop.opcode_len == 0) {
                    yasm__error(bc->line, N_("near jump does not exist"));
@@ -728,12 +740,26 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
            }
            break;
        default:
+           temp = yasm_expr_copy(jmprel->target);
+           temp = yasm_expr_simplify(temp, NULL);
+
+           /* Check for far displacement (seg:off). */
+           if (yasm_expr_is_op(temp, YASM_EXPR_SEGOFF)) {
+               jrtype = JR_FAR;
+               break;      /* length handled below */
+           } else if (jmprel->op_sel == JR_FAR) {
+               yasm__error(bc->line,
+                           N_("far jump does not have a far displacement"));
+               return YASM_BC_RESOLVE_ERROR | YASM_BC_RESOLVE_UNKNOWN_LEN;
+           }
+
            /* Try to find shortest displacement based on difference between
             * target expr value and our (this bytecode's) offset.  Note this
             * requires offset to be set BEFORE calling calc_len in order for
             * this test to be valid.
             */
-           temp = yasm_expr_copy(jmprel->target);
+           temp = yasm_expr_new(YASM_EXPR_SUB, yasm_expr_expr(temp),
+                                yasm_expr_sym(jmprel->origin), bc->line);
            num = yasm_expr_get_intnum(&temp, calc_bc_dist);
            if (num) {
                rel = yasm_intnum_get_int(num);
@@ -742,12 +768,12 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
                if (jmprel->shortop.opcode_len != 0 && rel >= -128 &&
                    rel <= 127) {
                    /* It fits into a short displacement. */
-                   jrshort = 1;
+                   jrtype = JR_SHORT;
                } else if (jmprel->nearop.opcode_len != 0) {
                    /* Near for now, but could get shorter in the future if
                     * there's a short form available.
                     */
-                   jrshort = 0;
+                   jrtype = JR_NEAR;
                    if (jmprel->shortop.opcode_len != 0)
                        retval = YASM_BC_RESOLVE_NONE;
                } else {
@@ -762,7 +788,7 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
                        return YASM_BC_RESOLVE_ERROR |
                            YASM_BC_RESOLVE_UNKNOWN_LEN;
                    }
-                   jrshort = 1;
+                   jrtype = JR_SHORT;
                }
            } else {
                /* It's unknown.  Thus, assume near displacement.  If a near
@@ -772,7 +798,7 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
                if (jmprel->nearop.opcode_len != 0) {
                    if (jmprel->shortop.opcode_len != 0)
                        retval = YASM_BC_RESOLVE_NONE;
-                   jrshort = 0;
+                   jrtype = JR_NEAR;
                } else {
                    if (save) {
                        yasm__error(bc->line,
@@ -780,28 +806,43 @@ x86_bc_resolve_jmprel(x86_jmprel *jmprel, unsigned long *len, int save,
                        return YASM_BC_RESOLVE_ERROR |
                            YASM_BC_RESOLVE_UNKNOWN_LEN;
                    }
-                   jrshort = 1;
+                   jrtype = JR_SHORT;
                }
            }
            yasm_expr_delete(temp);
            break;
     }
 
-    if (jrshort) {
-       if (save)
-           jmprel->op_sel = JR_SHORT;
-       if (jmprel->shortop.opcode_len == 0)
-           return YASM_BC_RESOLVE_UNKNOWN_LEN; /* that size not available */
+    switch (jrtype) {
+       case JR_SHORT:
+           if (save)
+               jmprel->op_sel = JR_SHORT;
+           if (jmprel->shortop.opcode_len == 0)
+               return YASM_BC_RESOLVE_UNKNOWN_LEN; /* size not available */
 
-       *len += jmprel->shortop.opcode_len + 1;
-    } else {
-       if (save)
-           jmprel->op_sel = JR_NEAR;
-       if (jmprel->nearop.opcode_len == 0)
-           return YASM_BC_RESOLVE_UNKNOWN_LEN; /* that size not available */
+           *len += jmprel->shortop.opcode_len + 1;
+           break;
+       case JR_NEAR:
+           if (save)
+               jmprel->op_sel = JR_NEAR;
+           if (jmprel->nearop.opcode_len == 0)
+               return YASM_BC_RESOLVE_UNKNOWN_LEN; /* size not available */
 
-       *len += jmprel->nearop.opcode_len;
-       *len += (opersize == 32) ? 4 : 2;
+           *len += jmprel->nearop.opcode_len;
+           *len += (opersize == 32) ? 4 : 2;
+           break;
+       case JR_FAR:
+           if (save)
+               jmprel->op_sel = JR_FAR;
+           if (jmprel->farop.opcode_len == 0)
+               return YASM_BC_RESOLVE_UNKNOWN_LEN; /* size not available */
+
+           *len += jmprel->farop.opcode_len;
+           *len += 2;  /* segment */
+           *len += (opersize == 32) ? 4 : 2;
+           break;
+       default:
+           yasm_internal_error(N_("unknown jump type"));
     }
     *len += (jmprel->addrsize != 0 && jmprel->addrsize != jmprel->mode_bits) ?
        1:0;
@@ -933,6 +974,7 @@ x86_bc_tobytes_jmprel(x86_jmprel *jmprel, unsigned char **bufp,
     unsigned char opersize;
     unsigned int i;
     unsigned char *bufp_orig = *bufp;
+    /*@null@*/ yasm_expr *targetseg;
 
     /* Prefixes */
     if (jmprel->lockrep_pre != 0)
@@ -960,6 +1002,9 @@ x86_bc_tobytes_jmprel(x86_jmprel *jmprel, unsigned char **bufp,
                YASM_WRITE_8(*bufp, jmprel->shortop.opcode[i]);
 
            /* Relative displacement */
+           jmprel->target =
+               yasm_expr_new(YASM_EXPR_SUB, yasm_expr_expr(jmprel->target),
+                             yasm_expr_sym(jmprel->origin), bc->line);
            if (output_expr(&jmprel->target, bufp, 1,
                            (unsigned long)(*bufp-bufp_orig), sect, bc, 1, d))
                return 1;
@@ -977,11 +1022,39 @@ x86_bc_tobytes_jmprel(x86_jmprel *jmprel, unsigned char **bufp,
                YASM_WRITE_8(*bufp, jmprel->nearop.opcode[i]);
 
            /* Relative displacement */
+           jmprel->target =
+               yasm_expr_new(YASM_EXPR_SUB, yasm_expr_expr(jmprel->target),
+                             yasm_expr_sym(jmprel->origin), bc->line);
            if (output_expr(&jmprel->target, bufp,
                            (opersize == 32) ? 4UL : 2UL,
                            (unsigned long)(*bufp-bufp_orig), sect, bc, 1, d))
                return 1;
            break;
+       case JR_FAR:
+           /* far absolute (4/6 byte depending on operand size) */
+           if (jmprel->farop.opcode_len == 0) {
+               yasm__error(bc->line, N_("far jump does not exist"));
+               return 1;
+           }
+
+           /* Opcode */
+           for (i=0; i<jmprel->farop.opcode_len; i++)
+               YASM_WRITE_8(*bufp, jmprel->farop.opcode[i]);
+
+           /* Absolute displacement: segment and offset */
+           jmprel->target = yasm_expr_simplify(jmprel->target, NULL);
+           targetseg = yasm_expr_extract_segment(&jmprel->target);
+           if (!targetseg)
+               yasm_internal_error(N_("could not extract segment for far jump"));
+           if (output_expr(&jmprel->target, bufp,
+                           (opersize == 32) ? 4UL : 2UL,
+                           (unsigned long)(*bufp-bufp_orig), sect, bc, 0, d))
+               return 1;
+           if (output_expr(&targetseg, bufp, 2UL,
+                           (unsigned long)(*bufp-bufp_orig), sect, bc, 0, d))
+               return 1;
+
+           break;
        default:
            yasm_internal_error(N_("unrecognized relative jump op_sel"));
     }
index cd10684a3bbc0a499f286351578d1e7f8381fb58..c5785cab95e45eac5a89f1795fc1c732ff154d50 100644 (file)
@@ -25,7 +25,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 #include <util.h>
-RCSID("$IdPath: yasm/modules/arch/x86/x86id.re,v 1.46 2003/03/31 05:36:29 peter Exp $");
+RCSID("$IdPath$");
 
 #define YASM_LIB_INTERNAL
 #define YASM_BC_INTERNAL
@@ -166,6 +166,7 @@ static unsigned long cpu_enabled = ~CPU_Any;
  *             0 = none
  *             1 = shift operation with a ,1 short form (instead of imm8).
  *             2 = large imm16/32 that can become a sign-extended imm8.
+ *             3 = can be far jump
  */
 #define OPT_Imm                0x0
 #define OPT_Reg                0x1
@@ -226,6 +227,7 @@ static unsigned long cpu_enabled = ~CPU_Any;
 #define OPAP_None      (0UL<<16)
 #define OPAP_ShiftOp   (1UL<<16)
 #define OPAP_SImm8Avail        (2UL<<16)
+#define OPAP_JmpFar    (3UL<<16)
 #define OPAP_MASK      (3UL<<16)
 
 typedef struct x86_insn_info {
@@ -857,12 +859,12 @@ static const x86_insn_info call_insn[] = {
     { CPU_Any, 0, 16, 0, {0, 0, 0}, 0, 1, {OPT_Imm|OPS_16|OPA_JmpRel, 0, 0} },
     { CPU_386, 0, 32, 0, {0, 0, 0}, 0, 1, {OPT_Imm|OPS_32|OPA_JmpRel, 0, 0} },
 
-    { CPU_Any, 0, 16, 1, {0xE8, 0, 0}, 0, 1,
-      {OPT_Imm|OPS_16|OPTM_Near|OPA_JmpRel, 0, 0} },
-    { CPU_386, 0, 32, 1, {0xE8, 0, 0}, 0, 1,
-      {OPT_Imm|OPS_32|OPTM_Near|OPA_JmpRel, 0, 0} },
-    { CPU_Any, 0, 0, 1, {0xE8, 0, 0}, 0, 1,
-      {OPT_Imm|OPS_Any|OPTM_Near|OPA_JmpRel, 0, 0} },
+    { CPU_Any, 0, 16, 1, {0xE8, 0x9A, 0}, 0, 1,
+      {OPT_Imm|OPS_16|OPTM_Near|OPA_JmpRel|OPAP_JmpFar, 0, 0} },
+    { CPU_386, 0, 32, 1, {0xE8, 0x9A, 0}, 0, 1,
+      {OPT_Imm|OPS_32|OPTM_Near|OPA_JmpRel|OPAP_JmpFar, 0, 0} },
+    { CPU_Any, 0, 0, 1, {0xE8, 0x9A, 0}, 0, 1,
+      {OPT_Imm|OPS_Any|OPTM_Near|OPA_JmpRel|OPAP_JmpFar, 0, 0} },
 
     { CPU_Any, 0, 16, 1, {0xFF, 0, 0}, 2, 1, {OPT_RM|OPS_16|OPA_EA, 0, 0} },
     { CPU_386|CPU_Not64, 0, 32, 1, {0xFF, 0, 0}, 2, 1,
@@ -879,7 +881,12 @@ static const x86_insn_info call_insn[] = {
     { CPU_Any, 0, 0, 1, {0xFF, 0, 0}, 2, 1,
       {OPT_Mem|OPS_Any|OPTM_Near|OPA_EA, 0, 0} },
 
-    /* TODO: Far Imm 16:16/32 */
+    { CPU_Any, 0, 16, 1, {0x9A, 0, 0}, 3, 1,
+      {OPT_Imm|OPS_16|OPTM_Far|OPA_JmpRel, 0, 0} },
+    { CPU_386, 0, 32, 1, {0x9A, 0, 0}, 3, 1,
+      {OPT_Imm|OPS_32|OPTM_Far|OPA_JmpRel, 0, 0} },
+    { CPU_Any, 0, 0, 1, {0x9A, 0, 0}, 3, 1,
+      {OPT_Imm|OPS_Any|OPTM_Far|OPA_JmpRel, 0, 0} },
 
     { CPU_Any, 0, 16, 1, {0xFF, 0, 0}, 3, 1,
       {OPT_Mem|OPS_16|OPTM_Far|OPA_EA, 0, 0} },
@@ -895,12 +902,12 @@ static const x86_insn_info jmp_insn[] = {
 
     { CPU_Any, 0, 0, 1, {0xEB, 0, 0}, 0, 1,
       {OPT_Imm|OPS_Any|OPTM_Short|OPA_JmpRel, 0, 0} },
-    { CPU_Any, 0, 16, 1, {0xE9, 0, 0}, 0, 1,
-      {OPT_Imm|OPS_16|OPTM_Near|OPA_JmpRel, 0, 0} },
-    { CPU_386, 0, 32, 1, {0xE9, 0, 0}, 0, 1,
-      {OPT_Imm|OPS_32|OPTM_Near|OPA_JmpRel, 0, 0} },
-    { CPU_Any, 0, 0, 1, {0xE9, 0, 0}, 0, 1,
-      {OPT_Imm|OPS_Any|OPTM_Near|OPA_JmpRel, 0, 0} },
+    { CPU_Any, 0, 16, 1, {0xE9, 0xEA, 0}, 0, 1,
+      {OPT_Imm|OPS_16|OPTM_Near|OPA_JmpRel|OPAP_JmpFar, 0, 0} },
+    { CPU_386, 0, 32, 1, {0xE9, 0xEA, 0}, 0, 1,
+      {OPT_Imm|OPS_32|OPTM_Near|OPA_JmpRel|OPAP_JmpFar, 0, 0} },
+    { CPU_Any, 0, 0, 1, {0xE9, 0xEA, 0}, 0, 1,
+      {OPT_Imm|OPS_Any|OPTM_Near|OPA_JmpRel|OPAP_JmpFar, 0, 0} },
 
     { CPU_Any, 0, 16, 1, {0xFF, 0, 0}, 4, 1, {OPT_RM|OPS_16|OPA_EA, 0, 0} },
     { CPU_386|CPU_Not64, 0, 32, 1, {0xFF, 0, 0}, 4, 1,
@@ -917,7 +924,12 @@ static const x86_insn_info jmp_insn[] = {
     { CPU_Any, 0, 0, 1, {0xFF, 0, 0}, 4, 1,
       {OPT_Mem|OPS_Any|OPTM_Near|OPA_EA, 0, 0} },
 
-    /* TODO: Far Imm 16:16/32 */
+    { CPU_Any, 0, 16, 1, {0xEA, 0, 0}, 3, 1,
+      {OPT_Imm|OPS_16|OPTM_Far|OPA_JmpRel, 0, 0} },
+    { CPU_386, 0, 32, 1, {0xEA, 0, 0}, 3, 1,
+      {OPT_Imm|OPS_32|OPTM_Far|OPA_JmpRel, 0, 0} },
+    { CPU_Any, 0, 0, 1, {0xEA, 0, 0}, 3, 1,
+      {OPT_Imm|OPS_Any|OPTM_Far|OPA_JmpRel, 0, 0} },
 
     { CPU_Any, 0, 16, 1, {0xFF, 0, 0}, 5, 1,
       {OPT_Mem|OPS_16|OPTM_Far|OPA_EA, 0, 0} },
@@ -1529,11 +1541,22 @@ x86_new_jmprel(const unsigned long data[4], int num_operands,
     op = yasm_ops_first(operands);
     if (op->type != YASM_INSN__OPERAND_IMM)
        yasm_internal_error(N_("invalid operand conversion"));
-    d.target = yasm_expr_new(YASM_EXPR_SUB, yasm_expr_expr(op->data.val),
-       yasm_expr_sym(yasm_symrec_define_label("$", cur_section, prev_bc,
-                                              0, lindex)), lindex);
 
-    /* See if the user explicitly specified short/near. */
+    /* Far target needs to become "seg imm:imm". */
+    if ((jrinfo->operands[0] & OPTM_MASK) == OPTM_Far)
+       d.target = yasm_expr_new_tree(
+           yasm_expr_new_branch(YASM_EXPR_SEG, op->data.val, lindex),
+           YASM_EXPR_SEGOFF, yasm_expr_copy(op->data.val), lindex);
+    else
+       d.target = op->data.val;
+
+    /* Need to save jump origin for relative jumps. */
+    d.origin = yasm_symrec_define_label("$", cur_section, prev_bc, 0, lindex);
+
+    /* Initially assume no far opcode is available. */
+    d.far_op_len = 0;
+
+    /* See if the user explicitly specified short/near/far. */
     switch ((int)(jrinfo->operands[0] & OPTM_MASK)) {
        case OPTM_Short:
            d.op_sel = JR_SHORT_FORCED;
@@ -1541,6 +1564,13 @@ x86_new_jmprel(const unsigned long data[4], int num_operands,
        case OPTM_Near:
            d.op_sel = JR_NEAR_FORCED;
            break;
+       case OPTM_Far:
+           d.op_sel = JR_FAR;
+           d.far_op_len = info->opcode_len;
+           d.far_op[0] = info->opcode[0];
+           d.far_op[1] = info->opcode[1];
+           d.far_op[2] = info->opcode[2];
+           break;
        default:
            d.op_sel = JR_NONE;
     }
@@ -1603,6 +1633,10 @@ x86_new_jmprel(const unsigned long data[4], int num_operands,
                d.near_op[2] = info->opcode[2];
                if (info->modifiers & MOD_Op1Add)
                    d.near_op[1] += (unsigned char)(mod_data & 0xFF);
+               if ((info->operands[0] & OPAP_MASK) == OPAP_JmpFar) {
+                   d.far_op_len = 1;
+                   d.far_op[0] = info->opcode[info->opcode_len];
+               }
                break;
        }
     }