不要尝试将 64 位机器代码放入编译器生成的函数中。它可能会起作用,因为函数 prologue/epilogue 的编码在 32 位和 64 位中是相同的,但只有一个单独的 64 位代码块会更简洁。
最简单的方法可能是将该块组装到一个单独的文件中,使用 GAS .code64 或 NASM BITS 64 在目标文件中获取 64 位代码,您可以链接到 32 位可执行文件。
您说in a comment 您正在考虑将其用于从 32 位用户空间进程中针对 64 位内核的内核利用,因此您只需要在进程内存的可执行部分中使用一些代码字节以及获取指向该块的指针的方法。这当然是合理的;如果您可以从 32 位进程中获得对内核 RIP 的控制,这就是您想要的,因为内核代码将始终以长模式运行。
如果您在以 32 位模式启动的进程中使用 64 位用户空间代码执行某些操作,您可能可以将 far jmp 转换为 64 位代码块 (as @RossRidge suggests),使用内核的已知值__USER_CS 64 位代码段描述符。来自 64 位代码的 syscall 应该以 64 位模式返回,但如果不是,请尝试 int 0x80 ABI。它总是返回到您所处的模式,保存/恢复cs 和ss 以及rip 和rflags。 (What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)
.rodata 是可执行文件测试段的一部分,因此只需让编译器将字节放入const 数组中。有趣的事实:const int main = 195; 编译为一个没有段错误退出的程序,因为195 = 0xc3 = ret 的 x86 编码(并且 x86 是 little-endian)。对于任意长度的机器码序列,const char funcname[] = { 0x90, 0x90, ..., 0xc3 } 可以工作。 const 是必需的,否则它将进入 .data (read/write/noexec) 而不是 .rodata。
您可以使用const char funcname[] __attribute__((section(".text"))) = { ... }; to control what section it 进入(例如.text 以及编译器生成的函数),甚至可以使用链接器脚本来获得更多控制。
如果您真的想在一个 .c 文件中完成所有操作,而不是使用单独组装的纯 asm 源的更简单解决方案:
要将一些 64 位代码与编译器生成的 32 位代码组合在一起,请在 asm 语句 *在任何函数之外中使用 the .code64 GAS directive。 IDK,如果 gcc 发出您的 asm 时哪个部分将处于活动状态有任何保证 gcc 将如何将该 asm 与其 asm 混合,但它不会将其放在函数的中间。
asm(".pushsection .text \n\t" // AFAIK, there's no guarantee how this will mix with compiler asm output
".code64 \n\t"
".p2align 4 \n\t"
".globl my_codebytes \n\t" // optional
"my_codebytes: \n\t"
"inc %r10d \n\t"
"my_codebytes_end: \n\t"
//"my_codebytes_len: .long . - my_codebytes\n\t" // store the length in memory. Optional
".popsection \n\t"
#ifdef __i386
".code32" // back to 32-bit interpretation for gcc's code
// "\n\t inc %r10" // uncomment to check that it *doesn't* assemble
#endif
);
#ifdef __cplusplus
extern "C" {
#endif
// put C names on the labels.
// They are *not* pointers, their addresses are link-time constants
extern char my_codebytes[], my_codebytes_end[];
//extern const unsigned my_codebytes_len;
#ifdef __cplusplus
}
#endif
// This expression for the length isn't a compile-time constant, so this isn't legal C
//static const unsigned len = &my_codebytes_end - &my_codebytes;
#include <stddef.h>
#include <unistd.h>
int main(void) {
size_t len = my_codebytes_end - my_codebytes;
const char* bytes = my_codebytes;
// do whatever you want. Writing it to stdout is one option!
write(1, bytes, len);
}
这使用 gcc 和 clang (compiler explorer) 进行编译和汇编。
我在我的桌面上尝试过仔细检查:
peter@volta$ gcc -m32 -Wall -O3 /tmp/foo.c
peter@volta$ ./a.out | hd
00000000 41 ff c2 |A..|
00000003
这是inc %r10d 的正确编码:)
该程序在没有-m32 的情况下也可以运行,因为我使用#ifdef 来决定最后是否使用.code32。 (没有像 section 那样的 push/pop 模式指令。)
当然,反汇编二进制会告诉你:
00000580 <my_codebytes>:
580: 41 inc ecx
581: ff c2 inc edx
因为反汇编程序不知道为该块切换到 64 位反汇编。 (我想知道 ELF 是否有相应的属性……我没有使用任何汇编指令或链接器脚本来生成这样的属性,如果存在的话。)