- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
对于我学校的任务,我需要编写一个 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 位的。 (链接到 x86 标签 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)是当你有一个 red-zone .
您传递的是 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/
我刚刚在学习 NASM,我有点想弄清楚这个问题。你如何在 NASM 中声明变量?例如,如何在 NASM 中声明 unsigned int i?谢谢 最佳答案 汇编语言中没有 unsigned int
我正在阅读这篇关于操作系统编程的精彩脚本 http://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf 第 12 页有一
我正在尝试使用 nasm 打印给我的程序的命令行参数: GLOBAL main EXTERN printf section .rodata fmt db "Argument: %s", 10, 0 s
这两种工具都将汇编指令直接翻译成机器代码,但是否有可能确定哪一种产生最快和最干净的代码? 最佳答案 当您使用汇编程序编写时,您准确地描述了生成 的说明所以它不依赖于汇编程序。这取决于你。您编写的助记符
代码: %define x 0x03 x equ 0x03 它们之间有什么区别? 最佳答案 %define是一种更强大的宏处理方式,类似于 C 预处理器。在您的简单情况下,使用 x 没有太大区
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 关闭 8 年前。 这个问题似乎是题外话,因为它缺乏足够的信息来诊断问题。更详细地描述您的问题或inclu
我目前正在关注 a tutorial on OS development ,其中包括对引导加载程序的讨论。 我的引导加载程序当前处于 16 位实模式,因此,我能够使用提供的 BIOS 中断(例如 VG
len: equ 2 len: db 2 它们是否相同,产生的标签可以代替2使用?如果不是,那么每种声明形式的优点或缺点是什么?它们可以互换使用吗? 最佳答案 第一个是equate,类似于C
我的循环有问题,其中包含的代码很长,它给了我错误“短跳超出范围”,所以我想知道是否有一种方法可以通过不减少来使循环工作其中代码量有多少? 示例1: label: my code LOOP la
在阅读了至少约4本关于汇编编程的不同书籍的前3或4章之后,我进入了一个阶段,可以使用MASM 6.11将“Hello World”放置在dosbox控制台上。想象一下我的喜悦! 我程序的第一个版本使用
我正在做一个项目,将我编写的子程序附加到老师包含的主文件中。他给了我们使我们的子程序成为全局性的说明,但显然我是个白痴。这两个 asm 文件在同一个文件夹中,我正在使用 nasm -f elf -g
我最近开始使用 NASM 程序集编写代码,但我的问题是我不知道如何以正确的方式访问结构元素。我已经在这个网站和谷歌上搜索了解决方案,但我看到到处都有人说不同的话。我的程序崩溃了,我感觉问题出在访问结构
我正在尝试从用户那里获取输入,然后我想根据用户输入的内容输出一些文本。 我的问题是出于某种原因,它总是认为它是 A,我不知道为什么。你可以在下面找到我的代码: bits 16
我熟悉 TASM 但不太熟悉 NASM。我读过 NASM 允许使用本地标签,这些标签在名称前用点表示。例如代码 .loop: ;some code jmp .loop 定义一个局部标号,
您将如何在寄存器上对 NASM 进行位移?我读了手册,似乎只提到了这些运算符 >> , > 和 >仅用于整数常量。这就是“标量值”的含义。您可以使用 shl 移位寄存器中的值或 shr指示。它们用于将
首先,这是一个家庭作业。 我有一个循环来分别获取两位数的值,并通过将第一个数字乘以 10 并与第二个数字相加得到一个整数来加入它们。 我正在做这一切并保存在我的AL注册,现在我想将该整数插入一个数组,
我一直在做基本的 NASM 编码,我想知道是否可以使用 NASM 模拟按键。如果是这样,怎么做? 如果重要的话,我正在使用 Ubuntu linux 10.04 和 Pentium R T4300 处
我可以在 NASM 中创建一个新标签,它指向一个新的内存位置,该位置与另一个标签指向的内存位置偏移几个字节。 例如:如果 label1 指向内存位置 0x40h,有没有办法使用 label1 定义指向
我需要设置一些标签地址/偏移量的最高位。 我试过: 测试.nasm: BITS 32 dw mylabel | 0x8000 mylabel: dd 0 但是当我尝试组装它时,我得到: nasm -f
如果能向我解释以下使用 printf、使用 nasm 和 gcc 编译的示例中发生了什么,我将不胜感激。为什么“sud”只打印在屏幕上?我也不明白,当我将“push 'sud'”与“push 'sud
我是一名优秀的程序员,十分优秀!