gpt4 book ai didi

C 使用 NASM 指向 EFLAGS 的指针

转载 作者:太空狗 更新时间:2023-10-29 16:10:49 28 4
gpt4 key购买 nike

对于我学校的任务,我需要编写一个 C 程序,该程序使用汇编程序执行 16 位加法。除了结果外,还应返回 EFLAGS。

这是我的 C 程序:

int add(int a, int b, unsigned short* flags);//function must be used this way
int main(void){
unsigned short* flags = NULL;
printf("%d\n",add(30000, 36000, flags);// printing just the result
return 0;
}

目前程序只打印出结果而不是标志,因为我无法获得它们。

这是我使用 NASM 的汇编程序:

global add
section .text

add:
push ebp
mov ebp,esp
mov ax,[ebp+8]
mov bx,[ebp+12]
add ax,bx
mov esp,ebp
pop ebp
ret

现在一切顺利。但是我不知道如何获取必须在 [ebp+16] 处指向标志寄存器的指针。教授说我们必须使用 pushfd 命令。

我的问题就出在汇编代码上。得到标志的解决方案后,我将修改C程序以发出标志。

最佳答案

通常,您只需使用调试器来查看标志,而不是编写所有代码以将它们放入 C 变量中以进行调试打印。特别是因为体面的调试器会为您象征性地解码条件标志,而不是或同时显示十六进制值。

您不必知道或关心 FLAGS 中的哪个位是 CF,哪个位是 ZF。 (这些信息也与编写真实程序无关。我没有记住它,我只知道哪些标志由不同的条件测试,如 jae or jl 。当然,最好理解 FLAGS 只是数据您可以复制、保存/恢复,甚至可以根据需要进行修改)


您的函数参数和返回值是 int,在您使用的 System V 32 位 x86 ABI 中是 32 位的。 (链接到 标签 wiki 中的 ABI 文档)。编写一个只查看其输入的低 16 位并在输出的高 16 位中留下高位垃圾的函数是一个错误。原型(prototype)中的 int 返回值告诉编译器 EAX 的所有 32 位都是返回值的一部分。

正如 Michael 所指出的,您似乎是在说您的作业需要使用 16 位 ADD。与查看完整的 32 位相比,这将产生具有不同输入的进位、溢出和其他条件。 (顺便说一句,this article explains carry vs. overflow very well。)

这是我要做的。请注意 ADD 的 32 位操作数大小。

global add
section .text

add:
push ebp
mov ebp,esp ; stack frames are optional, you can address things relative to ESP

mov eax, [ebp+8] ; first arg: No need to avoid loading the full 32 bits; the next insn doesn't care about the high garbage.
add ax, [ebp+12] ; low 16 bits of second arg. Operand-size implied by AX

cwde ; sign-extend AX into EAX

mov ecx, [ebp+16] ; the pointer arg
pushf ; the simple straightforward way
pop edx
mov [ecx], dx ; Store the low 16 of what we popped. Writing word [ecx] is optional, because dx implies 16-bit operand-size
; be careful not to do a 32-bit store here, because that would write outside the caller's object.

; mov esp,ebp ; redundant: ESP is still pointing at the place we pushed EBP, since the push is balanced by an equal-size pop
pop ebp
ret

CWDE (8086 CBW 指令的 16->32 形式)不要与 CWD 混淆。 (AX -> DX:AX 8086 指令)。如果您不使用 AX,那么 MOVSX/MOVZX 是执行此操作的好方法。


有趣的方式:我们可以直接向目标内存地址执行 16 位弹出操作,而不是使用默认操作数大小并进行 32 位入栈和出栈操作。这会使堆栈不平衡,因此我们可以再次取消对 mov esp, ebp 的注释,或者使用 16 位 pushf(带有操作数大小前缀,according to the docs 使其仅推送低 16 位标志,而不是 32 位 EFLAGS。)

; What I'd *really* do: maximum efficiency if I had to use the 32-bit ABI with args on the stack, instead of args in registers
global add
section .text

add:
mov eax, [esp+4] ; first arg, first thing above the return address
add ax, [esp+8] ; second arg
cwde ; sign-extend AX into EAX

mov ecx, [esp+12] ; the pointer

pushfw ; push the low 16 of FLAGS
pop word [ecx] ; pop into memory pointed to by unsigned short* flags

ret

PUSHFW 和 POP WORD 都将使用操作数大小前缀进行汇编。 objdump -Mintel 的输出,它使用与 NASM 略有不同的语法:

  4000c0:       66 9c                   pushfw 
4000c2: 66 8f 01 pop WORD PTR [ecx]

PUSHFW 与 o16 PUSHF 相同。在 NASM 中,o16 应用操作数大小前缀。


如果你只需要低 8 位标志(不包括 OF),你可以使用 LAHF将 FLAGS 加载到 AH 中并存储它。


我不推荐直接推送到目的地。暂时将堆栈指向某个随机地址通常是不安全的。具有信号处理程序的程序将异步使用堆栈下方的空间。这就是为什么在将堆栈空间用于 sub esp, 32 或其他任何东西之前必须保留堆栈空间,即使您不打算进行会通过将更多内容压入堆栈来覆盖它的函数调用.唯一的异常(exception)是当你有一个 .


你的 C 调用者:

您传递的是 NULL 指针,因此您的 asm 函数当然会出现段错误。传递本地地址以将函数存储到某处。

int add(int a, int b, unsigned short* flags);

int main(void) {
unsigned short flags;
int result = add(30000, 36000, &flags);
printf("%d %#hx\n", result, flags);
return 0;
}

关于C 使用 NASM 指向 EFLAGS 的指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39470571/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com