我正在用 nasm 编码,但我不知道发生了什么。Linux 发行版是 Ubuntu 16 64 位,但 NASM 运行在 32 位。
预期输出 –>"Number is: 2"
实际产量 –>"Number is: 134520868"
代码:
%include "io.inc"
section .data
n1 db 2 ; i know it's a bad practice to define a db variable, it's just for a test
msg: db 'number is: %d',10,0
section .text
extern printf
global main
main:
push ebp
mov ebp, esp
push dword n1
push msg
call printf
mov esp, ebp
pop ebp
ret
我曾尝试用 定义 n1 dd
,或者推送 的内容n1
,甚至使用像这样的寄存器eax
。
更新,即使我这样做,push dword [n1]
唯一改变的是现在输出是“号码是:1836404226”……
Upadte,你让我意识到我在代码中输入了错误的“数字”,对此深表歉意
1
最佳答案
3
您需要一个 32 位数字,因此使用dd
,并且您不想打印变量的地址,而是打印其内容。所以:
n1 dd 2 ; ...
和
push dword [n1]
push msg
call printf
1
-
2或者您可以只进行零扩展或符号扩展加载。像 OP 建议的那样拥有一个
uint8_t
或int8_t
全局变量并不是坏习惯,至少它不比uint32_t
全局变量差。movzx eax, byte [n1]
/ 。或者只是要求 printf 使用或转换push eax
忽略高位字节,那么即使它只是一个,只要它后面肯定有一些垃圾,而不是未映射的页面。(链接 libc 的代码肯定会有一些紧随其后的 BSS 空间,所以这不是问题。)%hhd
%hhu
push dword [n1]
db
.data
–
|
我知道定义数据库变量是一种不好的做法,
这并不是坏习惯。
-
如果您想要一个字节变量,那么使用
db
是绝对正确的做法。 -
如果您不想要字节变量,那么使用仍然
db
不是
一种坏习惯;但这是一个错误。
这只是测试
它作为一种考验,并不会以某种方式颠倒宇宙法则并让错误的事情变得正确。
您printf()
打印了一些无意义的数字,因为您没有打印变量的内容:通过使用,push n1
您打印的是变量的地址。(地址往往是任意大的数字。)该数字134520868
是0x0804A024
十六进制的,这看起来像一个地址。
当你这样做时,push dword [n1]
你仍然会得到一些无意义的数字,因为n1
已经用 定义db
,所以它是一个字节,并且这个字节后面跟着其他字节(字母'n'
、'u'
和'm'
在你的情况下),当这四个字节一起读取时,dword
它们会形成一些不同于 2 的1836404226
其他数字。具体来说,你在这种情况下得到的数字是0x6D754E02
十六进制的,如果你把它分解成字节,你会得到2
、'N'
、'u'
和'm'
。(这意味着字符串是"Number"
而不是你在问题中显示的"number"
。)
您至少有两个选择:
-
使用 声明变量
dd
,然后使用push dword [n1]
。其余代码保持不变。 -
或者,保留用 定义的变量
db
,但使用movsx eax, byte [n1]
或movzx eax, byte [n1]
后跟push eax
将该字节符号扩展或零扩展为dword
,并将其推送到堆栈中。其余代码保持不变。
5
-
1由于您使用的是 x86 标签,因此您针对的是 16 位架构,其中 int 的长度为 16 位– 您在说什么?[x86-16] 专门指 16 位 x86。[x86] 包括所有模式,但如果没有其他标签,则单独使用通常意味着 32 位模式。而且 OP 说他们在 Linux 上以 32 位模式运行 NASM,所以
nasm -f el32
它不是16 位架构,而是int
32 位。您没有在 DOS 和引导加载程序之外使用过汇编吗?
–
-
取决于您的汇编程序的语法风格。- OP 说他们正在使用 NASM。而且,据我所知
push [word ptr n1]
在任何地方都无效。在 MASM 中,word ptr
超出 [],当不涉及寄存器时,它们是可选的。所以push word ptr [n1]
在 MASM 中是push word [n1]
在 NASM 中。这两者都不是您在 Linux 上 32 位 x86 想要的:push dword [n1]
,并且在格式字符串中使用"%hhd"
将其视为 8 位整数(忽略高垃圾),或者像您说的那样使用dd
。
– -
1@PeterCordes 呃,是的,我不知道我在说什么。我相信我现在已经解决了所有问题。
– -
“作为测试,它不会以某种方式颠覆宇宙法则,也不会使错误的事情变得正确。”你说得完全正确,我太紧张了,以至于忘记说明,我试图做的是打印一个 8 位长的无符号数,我认为用 8 位长的数字来做这件事是合乎逻辑的。无论如何,我很抱歉给您带来不便,我设法使它工作的唯一汇编程序是 ARM,这对我来说是一个全新的世界,所以我只是尽力而为。
– -
@leo:
movzx eax, byte [n1]
是 ARM 的等价物ldrb
。ARM 将前几个参数传递到寄存器中;大多数 32 位 x86 调用约定(包括 i386 SysV)仅使用堆栈,因此您需要零扩展并复制到堆栈。alonepush
只能复制,而不能零扩展较窄的源,这就是为什么您需要对内存中的 1 字节变量进行额外指令的原因。
–
|
首先,push n1
推送地址。 push dword [n1]
从内存中加载 4 个字节并推送它们。YASM
msg: db ...
紧随其后n1: db 2
,因此 4 字节 (dword) 负载会将 3 个字节的 ASCII 字符作为整数的高字节。%d
格式会将这 4 个字节全部作为int
要打印的。
%#x
x86 是小端字节序,因此如果您在格式字符串中使用,您会看到0x6d756e02
– 请注意,低字节是2
您加载的。 6e
采用'n'
ASCII / UTF-8 等。您还可以使用 GDB 查看之前的堆栈内存。有关 asm GDB 提示,请参阅call
的底部。
我知道定义 db 变量是一种不好的做法,它只是为了测试
char
拥有、uint8_t
或全局变量并不是坏习惯int8_t
。它并不比uint32_t
全局变量更糟糕。无论您的数据大小如何,您都需要使用适当的指令。这是汇编,没有编译器为您隐式转换类型。
在这种情况下,如果您不想引入其他垃圾,则只需加载一个字节。
您可以做的事情包括:
将一个字节进行零扩展或符号扩展,放入寄存器中,并将其推送
movzx eax, byte [n1]
/push eax
将从内存中加载一个字节,零扩展到 32 位(EAX 的宽度)。 movsx
相同,但带有符号扩展。
这是 C 编译器printf("...", n1)
对int8_t n1 = 2;
加载高位垃圾但告知printf
仅查看低位字节
因为您知道在这种情况下您的字节后有有效数据,所以您不会因超出页面末尾而进入未映射的页面而导致段错误。
push dword [n1]
并%hhd
在格式字符串中使用以将 arg 视为int8_t
,仅查看低字节。请参阅。
在变量末尾加载垃圾不是用 C 语言可以表达的,除非使用memcpy(&tmp_int, &n1, sizeof(tmp_var));
。如果您使用格式字符串,一个非常聪明的编译器可能会进行这种 asm 优化,%hhd
这样它就知道它推送的高 3 个字节无关紧要。您的n1
恰好是 4 字节对齐的(因为它位于此文件中的开头.data
),因此 4 字节加载不能跨缓存行拆分,所以没有缺点。
请注意,包括 i386 System V 在内的标准调用约定允许窄参数包含高垃圾;它们不必为零或符号扩展到 32 位。 (除了可能作为 clang 所需的未记录的扩展,至少是这种情况。) 无论如何,printf
用 C 术语来说,采用int
and %hd
/%hhd
转换会截断它,因为它是可变的,因此适用默认参数提升:窄整数类型提升为int
。)
为你的全局保留 4 个字节
n1: dd 2
允许从内存中push dword [n1]
加载0x0000002
,因此%d
转换为打印整个内容int
将仅打印2
。
用 C 语言static int n1 = 2;
来说,这是static int8_t n1 = 2;
|
msg
格式字符串那样以“number”开头,并且1836404226
是0x6d754e02
(with'N'
) 而不是0x6d756e02
(with'n'
)。我猜您也更改了格式字符串。–
|