Hel*_*mid 3 c x86 pointers nasm eflags
对于我学校的任务,我需要编写一个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;
}
Run Code Online (Sandbox Code Playgroud)
目前程序只打印出结果而不是标志,因为我无法得到它们.
这是我使用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
Run Code Online (Sandbox Code Playgroud)
现在一切顺利.但我不知道如何获得必须[ebp+16]指向标志寄存器的指针.教授说我们将不得不使用这个pushfd命令.
我的问题只在于汇编代码.在得到标志的解决方案后,我将修改C程序以发出标志.
通常,您只需使用调试器来查看标志,而不是编写所有代码以将它们转换为C变量以进行调试打印.特别是因为体面的调试器会为您象征性地解码条件标志,而不是显示十六进制值.
您不必知道或关心FLAGS中的哪个位是CF,哪个是ZF.(此信息是不相关的写实时节目,无论是.我没有记住它,我才知道这标志由像不同的条件下进行测试jae或jl.当然,这是很好的了解,标志只是数据,你可以复制,保存/恢复,甚至修改,如果你想)
您的函数args和返回值是int,在您使用的System V 32位x86 ABI中为32位.(链接到x86标签wiki 中的ABI文档).编写一个仅查看其输入的低16位的函数,并在输出的高16位中留下高垃圾是一个错误.int原型中的返回值告诉编译器EAX的所有32位都是返回值的一部分.
正如迈克尔指出的那样,你似乎在说你的任务要求使用16位ADD.与查看完整的32位相比,这将产生具有不同输入的进位,溢出和其他条件.(顺便说一句,这篇文章很好地解释了携带与溢出.)
这就是我要做的.请注意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
Run Code Online (Sandbox Code Playgroud)
CWDE(8086 CBW指令的16-> 32形式)不要与CWD(AX - > DX:AX 8086指令)混淆.如果你没有使用AX,那么MOVSX/MOVZX是一个很好的方法.
有趣的方式:我们可以直接在目标内存地址中执行16位弹出操作,而不是使用默认操作数大小并执行32位推送和弹出操作.这会使堆栈失去平衡,所以我们可以mov esp, ebp再次取消注释,或者使用16位pushf(带有操作数大小的前缀,根据文档使其仅推送低16 FLAGS,而不是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
Run Code Online (Sandbox Code Playgroud)
PUSHFW和POP WORD都将使用操作数大小前缀进行汇编.输出来自objdump -Mintel,使用与NASM略有不同的语法:
4000c0: 66 9c pushfw
4000c2: 66 8f 01 pop WORD PTR [ecx]
Run Code Online (Sandbox Code Playgroud)
PUSHFW与之相同o16 PUSHF.在NASM中,o16应用操作数大小前缀.
如果您只需要低8个标志(不包括OF),您可以使用LAHF将FLAGS加载到AH中并存储它.
推送直接进入目的地不是我推荐的.暂时将堆栈指向某个随机地址通常是不安全的.带信号处理程序的程序将异步使用堆栈下方的空间.这就是为什么你必须在使用它之前保留堆栈空间的原因sub esp, 32,即使你不打算通过在堆栈上推送更多内容来进行覆盖它的函数调用.唯一的例外是你有一个红区.
你正在传递一个NULL指针,所以当然你的asm函数是segfaults.传递一个本地的地址给函数某处存储.
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;
}
Run Code Online (Sandbox Code Playgroud)