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) {
/* 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; }