section .data
array dw 1,2,3,4,5,6,7,8,9,10 ; array of integers
msg db " numbers are : %d %d ",10,0
section .text
global main
extern printf ; for c printf
main:
push ebp
mov ebp,esp ;intialise stack
mov ax,11
push ax ;push ax with value 11
mov ax,22
push ax ;push ax with value 12
push msg
call printf ; calling printf function
add esp ,12
mov esp,ebp ;restore stack
pop ebp
当我推送立即值而不是通过 AX 推送时,它工作正常。那是为什么?
答案 0 :(得分:4)
正如Jester指出的那样:除非你知道自己在做什么,否则不要将16位值用16位代码压入堆栈。 AX 是一个16位寄存器(32位 EAX 寄存器的下半部分)。当你这样做时:
push ax
16位被压入堆栈,因为 AX 是一个16位寄存器。这将阻止printf
正确访问该值,因为数据不是32位宽。如果你这样做:
push 11
你发现这是有效的。当 NASM 生成32位代码时,它会假定当推入堆栈时立即值为32位宽。这就是为什么这种情况适合你。
如果你要 PUSH 一个32位寄存器,那么一个完整的32位数据将放在堆栈的顶部。举个例子:
push eax
您的意图似乎是访问或遍历 WORD 数组( WORD = 16位值)并使用%d
转换进行打印printf
使用的说明符。 %d
将32位 DWORDS 打印为已签名值。您必须将它们作为 WORD 加载到内存中,并在将它们推入堆栈之前将它们转换为 DWORDS 。
汇编语言没有传统意义上的更高级编程语言的变量概念。您赋予包含 WORD (16位值)的内存位置的含义。无论是有符号还是无符号,都取决于您用于与该数据交互的代码。
386有两条帮助指示。 MOVSX用于将签名扩展较小的操作数更大的操作数。当您希望保留 SIGN (正面或负面)时使用此选项。 MOVZX用于零扩展较小的操作数到较大的操作数。该指令用于无符号值,并且在转换期间只是将目标操作数的所有高位设置为零。
作为一个例子,我写了一些以一系列单词为中心的代码:
section .data
array dw -1,0,1,2,3,4,5,6,7,8,9,10,-32768,32767,32768
; array of integers
arraylen equ ($-array)/2 ; number of word elements in array
msg db " numbers are : %d %d ",10,0
section .text
global main
extern printf ; for c printf
main:
push ebp
mov ebp,esp ; intialise stack
push ebx ; ebx is caller saved register. We destroy it so
; we must restore it before our function exits
xor ebx, ebx ; index = 0
; Make the equivalent of a for loop to traverse array
.loop1:
cmp ebx, arraylen ; We'll process all the elements of the array
je .endloop ; End when our index = arraylen
movzx eax, word [array + ebx * 2] ; Use EBX as index into WORD array
; zero extend 16-bit array value into 32-bit register
push eax ; parameter 3 = unsigned DWORD
movsx eax, word [array + ebx * 2] ; Use EBX as index into WORD array
; sign extend 16-bit array value into 32-bit register
; movsx eax, ax ; The line above would have also worked this way
push eax ; parameter 2 = signed DWORD onto stack
push msg ; parameter 1 = pointer to format string
call printf ; calling printf function
add esp, 12
inc ebx ; index += 1
jmp .loop1 ; continue for loop
.endloop:
pop ebx ; Restore ebx
mov esp,ebp ; restore stack
pop ebp
代码确保按照CDECL calling convention被调用者保存的任何寄存器(上面代码中的 EBX )在函数的开头和结尾保存并恢复。关于这方面的解释的更多信息可以在我写的最近StackOverflow answer中找到。
我编写了for循环的等价物(你可以将它编码为do-while)或任何其他循环结构来遍历数组。我同时使用 MOVZX 和 MOVSX ,并以printf
格式字符串显示结果。
注意:: MOVZX 也可以通过清零目标操作数并将源操作数移入目标后完成。举个例子:
movzx eax, word [array + ebx * 2]
可以编码为:
xor eax, eax ; eax = 0
mov ax, word [array + ebx * 2]
应该能够组装和链接:
nasm -f elf32 testmov.asm
gcc -m32 -o testmov testmov.o
以./testmov
运行时,结果应如下所示:
numbers are : -1 65535 numbers are : 0 0 numbers are : 1 1 numbers are : 2 2 numbers are : 3 3 numbers are : 4 4 numbers are : 5 5 numbers are : 6 6 numbers are : 7 7 numbers are : 8 8 numbers are : 9 9 numbers are : 10 10 numbers are : -32768 32768 numbers are : 32767 32767 numbers are : -32768 32768
如果要使用printf打印未签名的 WORD (16位值),可以使用%hu
(无符号短)和签名 WORD 你可以使用%hd
(签名简称)。虽然您仍然需要为参数传递 DWORD ,但您不必担心零扩展(或符号扩展),因为printf
只会查看较低的2个字节您作为参数传递的 DWORD 的内容。
答案 1 :(得分:0)
您可以将16位寄存器推送到32位堆栈,但printf稍后会将其从堆栈中取出为完整的32位项目。这就是你获得垃圾值的地方 - printf拾取的物品的较高部分。这正是你的问题。因此,为了防止将来出现问题,请始终将32位CPU,64位项目上的32位项目推送到64位CPU,依此类推。对于立即,您的编译器将默认为32位立即,这就是您的程序与immediates一起工作的原因。