我有MSVC .asm代码
_get_addr:
call _lb
_lb:
pop rax
ret
及其头
PVOID _get_addr();
我想把它移植到GCC。
它在GCC中的替换应该是这样的,对吗?
static __inline__ __attribute__((always_inline)) PVOID _get_addr() {
PVOID pointer;
__asm__(
"call _lb"
"_lb: pop rax; ret %[pointer]"
: [pointer] "=r"(pointer)
);
return pointer;
}
编辑:
仅供参考,这不是我的代码,所以我不确定它是什么意思。我只知道asm实现和它的头。所以可能我的内联是不正确的。
我只是假设_get_addr()
应该返回它的调用者的地址。在这种情况下,内联是有意义的,对吗?但你可能是对的,我不应该改变这一点。
EDTI2
用于EDK2自定义组件
PVOID GetImageAddress(void)
{
PVOID Addr = _get_addr();
PVOID Base = (PVOID)((UINT64)Addr & ~(DEFAULT_EDK_ALIGN - 1));
// get current module base by address inside of it
while (*(PUSHORT)Base != EFI_IMAGE_DOS_SIGNATURE)
{
Base = (PVOID)((PUCHAR)Base - DEFAULT_EDK_ALIGN);
}
return Base;
}
那个MASM代码是愚蠢的,64位模式不需要call
读取它自己的地址。
您应该将其替换为与rip相关的LEA。
对于x86-64,在内联asm中push/pop也不安全(除非先将RSP偏移128以跳过红色区域);这将踩在x86-64 System V ABI中的RSP以下的红色区域,并且没有办法在此声明一个clober。避开call
可以避免按压任何东西
也因为你不想让它成为一个函数,你可以让编译器选择寄存器。您已经使用了"=r"
,因此在模板字符串中使用%0
来扩展到编译器选择的寄存器。硬编码RAX只有在编译器为"=r"
输出选择RAX时才有效,否则会造成灾难。
void *current_RIP;
// assuming the default -masm=att, not -masm=intel
asm( "lea 0(%%rip), %0" : "=r"(current_RIP) ); // no inputs, no clobbers
(你的GNU C内联asm尝试在其他方面也被打破了,例如ret %rax
不是一个有效的指令。在ret
上放置操作数是没有意义的。由于这是内联asm,并且您已经弹出了返回地址,因此根本不应该使用ret
指令。MASM代码需要它,因为它是一个返回给调用者的函数,而不是从内联asm语句的末尾掉出来。
避免内联asm
或者完全避免内联asm,只取一个代码地址。函数本身,如(void*)_get_addr
,或goto标签。(https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html)
static inline get_addr() {
PVOID pointer = &&label;
label:
return pointer;
}
注意,_names
在全局范围内被保留以供实现自己使用。除非这段代码是GCC或clang头文件的一部分,否则这是一个糟糕的名称选择。
Godbolt展示了它们如何使用gcc -O2
和/不使用-fPIE
进行编译。标签值将在非pie Linux可执行文件中使用mov eax, imm32
绝对寻址,因为地址是没有ASLR的链接时间常数。但是如果ASLR是可能的(PIE),它将像asm一样使用rip相对LEA。
跳转到那个地址显然是不安全的,所以IDK你打算用指针做什么,以及它需要有多精确。
正如David Wohlferd在注释中指出的,原始的独立asm函数总是返回相同的地址(内部)给每一个来电者。一个可以内联(甚至是强制内联)的函数将在该函数内部返回一个地址。
或者如果优化编译器选择进一步内联,甚至在更高的父函数中。这需要编译时内联,不像使用__builtin_return_address(0)
,所以至少它总是与C源代码中的实际调用地址相同的可执行文件或库中的地址。