【问题标题】:Encoding a CALL instruction to call a function编码 CALL 指令以调用函数
【发布时间】:2014-04-01 04:35:57
【问题描述】:

我正在尝试创建一个能够在运行时对指令进行编码的汇编器(用于 JIT 编译器)。对不起,长代码 sn-p,但这是显示我的问题的最短的可编译示例。

#include <stdint.h>
#include <iostream>

#include <windows.h>

typedef void (*function)();
uint8_t* instructionBuffer;
uint32_t pos;

/**
 * Creates the instruction buffer;
 */
void assembler_initialize() {
    instructionBuffer = (uint8_t*) VirtualAllocEx(GetCurrentProcess(), 0, 1024,
    MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    pos = 0;
}

/**
 * Writes a call to the given address to the instruction buffer
 */
void assembler_emit_call(uint32_t value) {
    // CALL opcode
    instructionBuffer[pos++] = 0xFF;

    // opcode extension 2, read a 32bit address
    instructionBuffer[pos++] = 0x15;

    // Address as little endian
    instructionBuffer[pos++] = (value >> 0) & 0xFF;
    instructionBuffer[pos++] = (value >> 8) & 0xFF;
    instructionBuffer[pos++] = (value >> 16) & 0xFF;
    instructionBuffer[pos++] = (value >> 24) & 0xFF;
}

/**
 * Writes a RET to the instruction buffer
 */
void assembler_emit_ret() {
    instructionBuffer[pos++] = 0xC3;
}

/**
 * The function to call
 */
void __cdecl myFunction() {
    std::cout << "Hello world!" << std::endl;
}

/**
 *
 */
int main(int argc, char **argv) {
    assembler_initialize();
    assembler_emit_call((uint32_t) &myFunction);
    assembler_emit_ret();

    // Output the address
    std::cout << std::hex << (uint32_t) &myFunction << std::endl;

    // Output the opcodes
    for (uint32_t i = 0; i < 100; i++) {
        std::cout << std::hex << (uint32_t) instructionBuffer[i] << " ";
    }
    std::cout << std::endl;

    // Call the function
    function f = (function) instructionBuffer;
    f();

    return 0;
}

输出告诉我,myFunction 的地址是0x4017c5,并且这些操作码是这样写的:

CALL   ModRM   Addr (le)     RET    Zeros
ff     15      c5 17 40 0    c3     0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ...

不过,我的程序在尝试执行代码时还是崩溃了。我在编码CALL 指令时是否遗漏了什么?

【问题讨论】:

  • 如果调用本身有效但 被调用 例程崩溃怎么办?你检查了吗?
  • 嗯,当简单地调用例程 myFunction 时,Hello world! 文本会被打印出来。
  • 你可以单步执行 JITed 代码吗?

标签: c instruction-set x86


【解决方案1】:

它不起作用,因为调用指令不正确。实际上 x86 上没有 CALL absolute_address 指令。

在您的示例中,您生成以下 X86 代码:

FF 15 xx xx xx xx

这是对地址 xx xx xx xx 的间接调用。这将获取在 xx xx xx xx 找到的地址并在那里进行呼叫。

示例

FF 15 10 20 30 00

这将查看地址 0x302010 :

00302010: 11 22 33 00 xx xx xx xx

它在哪里找到值 0x00332211 并在该地址调用函数。

assembler_emit_call 中进行以下修改后,程序可以正常工作。

void assembler_emit_call(uint32_t value) {
    // CALL opcode
    instructionBuffer[pos++] = 0xb8;  // mov  eax, address

    // Address as little endian
    instructionBuffer[pos++] = (value >> 0) & 0xFF;
    instructionBuffer[pos++] = (value >> 8) & 0xFF;
    instructionBuffer[pos++] = (value >> 16) & 0xFF;
    instructionBuffer[pos++] = (value >> 24) & 0xFF;

    instructionBuffer[pos++] = 0xff ;  // call eax
    instructionBuffer[pos++] = 0xd0 ;
    instructionBuffer[pos++] = 0xc3 ;  // ret
}

顺便说一句

    instructionBuffer[pos++] = (value >> 0) & 0xFF;
    instructionBuffer[pos++] = (value >> 8) & 0xFF;
    instructionBuffer[pos++] = (value >> 16) & 0xFF;
    instructionBuffer[pos++] = (value >> 24) & 0xFF;

可以替换为

    *(DWORD*)(instructionBuffer + pos) = value ;
    pos += 4 ;

【讨论】:

    猜你喜欢
    • 2018-05-10
    • 2014-12-31
    • 2011-09-21
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    • 2020-03-20
    • 2017-07-13
    • 2017-03-19
    相关资源
    最近更新 更多