从 Rust 更改 x86 中的执行堆栈
Changing execution stack in x86 from Rust
我正在尝试手动设置 RSP 并使用内联 x86_64 程序集在 Rust 中的自定义地址开始执行。
我有这个有效的 C 代码:
#include <stddef.h>
void __attribute ((noreturn)) jump_with_stack(size_t jump_addr, size_t *jump_stack) {
__asm__ volatile ( \
"movq %[stack], %%rsp\n" \
"xor %%rdx, %%rdx\n" \
"jmp *%[entry]" \
: /* None */ \
: [stack] "r" (jump_stack), [entry] "r" (jump_addr) \
: "rdx", "memory" \
);
}
这是反汇编:
jump_with_stack:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov QWORD PTR [rbp-16], rsi
mov rax, QWORD PTR [rbp-16]
mov rcx, QWORD PTR [rbp-8]
movq rax, %rsp
xor %rdx, %rdx
jmp *rcx
nop
pop rbp
ret
而这个 Rust 代码没有:
#![feature(asm)]
pub unsafe extern fn rust_jump_with_stack(target: usize, targ_stack: *mut usize) -> ! {
asm!("mov rsp, [=12=]
xor rdx, rdx
jmp []"
:
:"r"(targ_stack), "r"(target)
: "rdx", "memory"
: "intel");
unreachable!();
}
这里是铁锈拆解:
example::rust_jump_with_stack:
push rax
mov rsp, rsi
xor rdx, rdx
jmp qword ptr [rdi]
lea rdi, [rip + .L__unnamed_3]
lea rdx, [rip + .L__unnamed_4]
mov rax, qword ptr [rip + std::panicking::begin_panic@GOTPCREL]
mov esi, 40
call rax
ud2
(两个反汇编输出均来自 Godbolt explorer)
我不明白两者之间的区别或生成的代码中的区别意味着什么。
jmp []
是您的 AT&T 版本中没有的额外间接级别。
AT&T jmp *%1
相当于英特尔 jmp %1
.
注意%[entry]
只是%1
的符号写法;方括号是操作数名称语法的一部分,不会作为寻址模式语法出现在最终的 asm 输出中。
(你的问题也是一团糟,因为你在 Godbolt 上使用 -masm=intel
,而你的 GNU C 内联 asm 被编写为使用默认 -masm=att
构建。)
其他主要区别是 GCC 默认为 -O0
(反优化调试模式),而您在启用优化的情况下构建 Rust 代码。
您可能应该在 C asm("")
语句之后使用 __builtin_unreachable()
以确保编译器知道执行确实不会出现在 asm 语句的另一侧。我担心标记包装函数 noreturn
可能不足以阻止编译器假设它可以将存储推迟到 asm 语句之后,在内联之后。 (跳出 asm
语句通常需要 asm goto
已知标签,否则建议 __builtin_unreachable()
。)
我正在尝试手动设置 RSP 并使用内联 x86_64 程序集在 Rust 中的自定义地址开始执行。
我有这个有效的 C 代码:
#include <stddef.h>
void __attribute ((noreturn)) jump_with_stack(size_t jump_addr, size_t *jump_stack) {
__asm__ volatile ( \
"movq %[stack], %%rsp\n" \
"xor %%rdx, %%rdx\n" \
"jmp *%[entry]" \
: /* None */ \
: [stack] "r" (jump_stack), [entry] "r" (jump_addr) \
: "rdx", "memory" \
);
}
这是反汇编:
jump_with_stack:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov QWORD PTR [rbp-16], rsi
mov rax, QWORD PTR [rbp-16]
mov rcx, QWORD PTR [rbp-8]
movq rax, %rsp
xor %rdx, %rdx
jmp *rcx
nop
pop rbp
ret
而这个 Rust 代码没有:
#![feature(asm)]
pub unsafe extern fn rust_jump_with_stack(target: usize, targ_stack: *mut usize) -> ! {
asm!("mov rsp, [=12=]
xor rdx, rdx
jmp []"
:
:"r"(targ_stack), "r"(target)
: "rdx", "memory"
: "intel");
unreachable!();
}
这里是铁锈拆解:
example::rust_jump_with_stack:
push rax
mov rsp, rsi
xor rdx, rdx
jmp qword ptr [rdi]
lea rdi, [rip + .L__unnamed_3]
lea rdx, [rip + .L__unnamed_4]
mov rax, qword ptr [rip + std::panicking::begin_panic@GOTPCREL]
mov esi, 40
call rax
ud2
(两个反汇编输出均来自 Godbolt explorer)
我不明白两者之间的区别或生成的代码中的区别意味着什么。
jmp []
是您的 AT&T 版本中没有的额外间接级别。
AT&T jmp *%1
相当于英特尔 jmp %1
.
注意%[entry]
只是%1
的符号写法;方括号是操作数名称语法的一部分,不会作为寻址模式语法出现在最终的 asm 输出中。
(你的问题也是一团糟,因为你在 Godbolt 上使用 -masm=intel
,而你的 GNU C 内联 asm 被编写为使用默认 -masm=att
构建。)
其他主要区别是 GCC 默认为 -O0
(反优化调试模式),而您在启用优化的情况下构建 Rust 代码。
您可能应该在 C asm("")
语句之后使用 __builtin_unreachable()
以确保编译器知道执行确实不会出现在 asm 语句的另一侧。我担心标记包装函数 noreturn
可能不足以阻止编译器假设它可以将存储推迟到 asm 语句之后,在内联之后。 (跳出 asm
语句通常需要 asm goto
已知标签,否则建议 __builtin_unreachable()
。)