如何通过 gcc 从 x86 内联汇编中获取 EIP

How get EIP from x86 inline assembly by gcc

我想从下面的代码中获取EIP的值,但是编译不通过

命令: gcc -o xxx x86_inline_asm.c -m32 && ./xxx

文件内容x86_inline_asm.c:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    unsigned int eip_val;
    __asm__("mov %0,%%eip":"=r"(eip_val)); 
    return 0;
}

如何使用内联汇编获取EIP的值,并在x86下编译成功。 如何修改代码并使用命令完成?

我不知道这个的 gcc 内联汇编语法,但是对于 masm:

        call    next0
next0:  pop     eax           ;eax = eip for this line

在 Masm 的情况下,$ 表示当前位置,并且由于 call 是一个 5 字节指令,没有标签的替代语法是:

        call    $+5
        pop     eax

这听起来不太可能有用(相对于像 void *tmp = main 那样只获取整个函数的地址),但它是可能的。


只要得到一个标号地址,或者用.(当前行的地址),让链接器操心把正确的立即数放到机器码里。所以你不是在架构上阅读EIP,只是从即时阅读它当前拥有的价值。

asm volatile("mov $., %0" : "=r"(address_of_mov_instruction) );

AT&T 的语法是mov src, dst,所以你写的是汇编的话就是一个跳转。

(在架构上,EIP = 指令在执行时的 end,因此可以说你应该做

asm volatile(
  "mov f, %0  \n\t"      // reference label 1 forward
  "1:"               // GAS local label
  "=r"(address_after_mov)
);

我正在使用 asm volatile 以防此 asm 语句通过内联或其他方式在同一函数内多次重复。如果你想让每个案例得到一个不同的地址,它必须是volatile。否则,编译器会假定此 asm 语句的所有实例都产生相同的输出。通常这样就可以了。


在 32 位模式下的架构上,您没有针对 LEA 的 RIP 相对寻址,因此实际读取 EIP 的唯一好方法是调用/弹出。 Reading program counter directly。它不是通用寄存器,因此您不能仅将其用作 mov 或任何其他指令的源或目标。


但实际上您根本不需要内联汇编。 Is it possible to store the address of a label in a variable and use goto to jump to it? 展示了如何使用 GNU C 扩展,其中 &&label 取其地址。

int foo;
void *addr_inside_function() {
    foo++;

    lab1:  ;  // labels only go on statements, not declarations
    void *tmp = &&lab1;

    foo++;
    return tmp;
}

在函数之外,您无法安全地使用此地址进行任何操作;我把它作为一个例子返回,让编译器在 asm 中放置一个标签,看看会发生什么。如果该标签没有 goto,它仍然可以非常积极地优化函数,但您可能会发现它可用作函数其他地方的 asm goto(...) 的输入。

但是无论如何,它编译on Godbolt到这个asm

# gcc -O3 -m32
addr_inside_function:
.L2:
        addl    , foo
        movl    $.L2, %eax
        ret
#clang -O3 -m32
addr_inside_function:
        movl    foo, %eax
        leal    1(%eax), %ecx
        movl    %ecx, foo
.Ltmp0:                                 # Block address taken
        addl    , %eax
        movl    %eax, foo
        movl    $.Ltmp0, %eax        # retval = label address
        retl

所以 clang 加载全局,计算 foo+1 并存储它,然后在标签之后计算 foo+2 并存储它。 (而不是加载两次)。所以你仍然不能从任何地方有效地跳转到标签,因为它取决于 fooeax 中的旧值,以及存储 foo+2[= 所需的行为31=]