int hook_create_stub(uint8_t *tramp, const uint8_t *addr, int len)
{
    const uint8_t *base_addr = addr;

    while (len > 0) {
        int length = lde(addr);
        if(length == 0) return -1;

        // How many bytes left?
        len -= length;

        // Unconditional jump with 32-bit relative offset.
        if(*addr == 0xe9) {
            const uint8_t *target = addr + *(int32_t *)(addr + 1) + 5;
            tramp += asm_jump(tramp, target);
            addr += 5;
        }
        // Call with 32-bit relative offset.
        else if(*addr == 0xe8) {
            const uint8_t *target = addr + *(int32_t *)(addr + 1) + 5;
            tramp += asm_call(tramp, target);
            addr += 5;
        }
        // Conditional jump with 32bit relative offset.
        else if(*addr == 0x0f && addr[1] >= 0x80 && addr[1] < 0x90) {

#if __x86_64__
            pipe("CRITICAL:Conditional jump and calls in 64-bit are "
                 "considered unstable!");
#endif

            // TODO This can be stabilized by creating a 8-bit conditional
            // jump with 32/64-bit jumps at each target. However, this is
            // only required for 64-bit support and then only when this
            // instruction occurs at all in the original function - which is
            // currently not the case.

            // Conditional jumps consist of two bytes.
            *tramp++ = addr[0];
            *tramp++ = addr[1];

            // When a jmp/call is performed, then the relative offset +
            // the instruction pointer + the size of the instruction is the
            // resulting address, so that's our target address.
            // As we have already written the first one or two bytes of the
            // instruction we only have the relative address left - four bytes
            // in total.
            const uint8_t *target = addr + *(int32_t *)(addr + 2) + 6;

            // We have already copied the instruction opcode(s) itself so we
            // just have to calculate the relative address now.
            *(uint32_t *) tramp = target - tramp - 4;
            tramp += 4;

            addr += 6;
        }
        // Unconditional jump with 8bit relative offset.
        else if(*addr == 0xeb) {
            const uint8_t *target = addr + *(int8_t *)(addr + 1) + 2;
            tramp += asm_jump(tramp, target);
            addr += 2;

            // TODO Check the remaining length. Also keep in mind that any
            // following nop's behind this short jump can be included in the
            // remaining available space.
        }
        // Conditional jump with 8bit relative offset.
        else if(*addr >= 0x70 && *addr < 0x80) {

#if __x86_64__
            pipe("CRITICAL:Conditional jumps in 64-bit are "
                 "considered unstable!");
#endif

            // TODO The same as for the 32-bit conditional jumps.

            // Same rules apply as with the 32bit relative offsets, except
            // for the fact that both conditional and unconditional 8bit
            // relative jumps take only one byte for the opcode.

            // Hex representation of the two types of 32bit jumps;
            // 8bit relative conditional jumps:     70..80
            // 32bit relative conditional jumps: 0f 80..90
            // Thus we have to add 0x10 to the opcode of 8bit relative
            // offset jump to obtain the 32bit relative offset jump
            // opcode.
            *tramp++ = 0x0f;
            *tramp++ = addr[0] + 0x10;

            // 8bit relative offset - we have to sign-extend it, by casting it
            // as signed char, in order to calculate the correct address.
            const uint8_t *target = addr + *(int8_t *)(addr + 1) + 2;

            // Calculate the relative address.
            *(uint32_t *) tramp = (uint32_t)(target - tramp - 4);
            tramp += 4;

            addr += 2;
        }
#if __x86_64__
        // In 64-bit mode we have RIP-relative mov and lea instructions. These
        // have to be relocated properly. Handles "mov reg64, qword [offset]"
        // and "lea reg64, qword [offset]".
        else if((*addr == 0x48 || *addr == 0x4c) &&
                (addr[1] == 0x8b || addr[1] == 0x8d) &&
                (addr[2] & 0xc7) == 0x05) {
            // Register index and full address.
            uint32_t reg = ((addr[2] >> 3) & 7) + (*addr == 0x4c ? 8 : 0);
            const uint8_t *target = addr + *(int32_t *)(addr + 3) + 7;

            // mov reg64, address
            tramp[0] = 0x48 + (reg >= 8);
            tramp[1] = 0xb8 + (reg & 7);
            *(const uint8_t **)(tramp + 2) = target;
            tramp += 10;

            // If it was a mov instruction then also emit the pointer
            // dereference part.
            if(addr[1] == 0x8b) {
                // mov reg64, qword [reg64]
                tramp[0] = reg < 8 ? 0x48 : 0x4d;
                tramp[1] = 0x8b;
                tramp[2] = (reg & 7) | ((reg & 7) << 3);
                tramp += 3;
            }
            addr += 7;
        }
#endif
        // Return instruction indicates the end of basic block as well so we
        // have to check if we already have enough space for our hook..
        else if((*addr == 0xc3 || *addr == 0xc2) && len > 0) {
Example #2
0
/* Entry point for the assembler. code_ref is assumed to be attached
   to the gc. */
size_t asm_string(gc_type *gc, char *str, uint8_t **code_ref) {
    yyscan_t scanner = 0;
    op_type token = 0;
    buffer_type *buf = 0;
    hashtable_type *labels = 0;
    jump_type *jump_list = 0;
    size_t length = 0;
    
    
    gc_register_root(gc, &buf);
    gc_register_root(gc, &labels);
    gc_register_root(gc, (void **)&jump_list);

    /* create an output buffer */
    buffer_create(gc, &buf);
    hash_create_string(gc, &labels);

    yylex_init(&scanner);
    /* yyset_debug(1, scanner); */


    /* set the scanners input */
    yy_scan_string(str, scanner);

    /* match until there is nothing left to match */
    while((token = yylex(scanner)) != END_OF_FILE) {

        /* Handle individual tokens */
        switch((int)token) {
        case OP_LIT_FIXNUM:
            asm_lit_fixnum(buf, scanner);
            break;

        case OP_LIT_CHAR:
            asm_lit_char(buf, scanner);
            break;

        case STRING_START_TOKEN:
            EMIT(buf, OP_LIT_STRING, 1);
            asm_lit_string(buf, scanner);
            break;

        case SYMBOL_START_TOKEN:
            EMIT(buf, OP_LIT_SYMBOL, 1);
            asm_lit_string(buf, scanner);
            break;

        case OP_JMP:            
        case OP_JNF:
        case OP_CALL:
        case OP_PROC:
        case OP_CONTINUE:
            EMIT(buf, token, 1); /* emit the jump operation */
            asm_jump(gc, buf, scanner, &jump_list);
            break;                

        case LABEL_TOKEN:
            asm_label(gc, buf, labels, get_text(scanner));
            break;

            /* All otherwise not defined tokens are
               their opcode */
        default:
            EMIT(buf, token, 1);
            break;

        }
    }

    yylex_destroy(scanner);


    /* build a code_ref */
    length = buffer_size(buf);
    /* *code_ref = gc_alloc(gc, 0, length); */
    gc_alloc(gc, 0, length, (void **)code_ref);
    length = buffer_read(buf, *code_ref, length);

    /* replace jump address fields */
    rewrite_jumps(*code_ref, jump_list, labels);

    gc_unregister_root(gc, &buf);
    gc_unregister_root(gc, &labels);
    gc_unregister_root(gc, (void **)&jump_list);

    return length;
}