我正在用 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

  • 您的程序与最终输出不匹配:文本以“Number”开头,而不是像您的msg格式字符串那样以“number”开头,并且18364042260x6d754e02(with 'N') 而不是0x6d756e02(with 'n')。我猜您也更改了格式字符串。


    – 


最佳答案
3

您需要一个 32 位数字,因此使用dd,并且您不想打印变量的地址,而是打印其内容。所以:

n1 dd 2 ; ...

push dword [n1]
push msg
call printf

1

  • 2
    或者您可以只进行零扩展或符号扩展加载。像 OP 建议的那样拥有一个uint8_tint8_t全局变量并不是坏习惯,至少它不比uint32_t全局变量差。 movzx eax, byte [n1]/ 。或者只是要求 printf 使用或转换push eax忽略高位字节,那么即使它只是一个,只要它后面肯定有一些垃圾,而不是未映射的页面。(链接 libc 的代码肯定会有一些紧随其后的 BSS 空间,所以这不是问题。)%hhd%hhupush dword [n1]db.data


    – 


我知道定义数据库变量是一种不好的做法,

这并不是坏习惯。

  • 如果您想要一个字节变量,那么使用db是绝对正确的做法。

  • 如果您想要字节变量,那么使用仍然db不是
    一种坏习惯;但这是一个错误

这只是测试

它作为一种考验,并不会以某种方式颠倒宇宙法则并让错误的事情变得正确。

printf()打印了一些无意义的数字,因为您没有打印变量的内容:通过使用,push n1您打印的是变量的地址。(地址往往是任意大的数字。)该数字1345208680x0804A024十六进制的,这看起来像一个地址。

当你这样做时,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 位架构,而是int32 位。您没有在 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)仅使用堆栈,因此您需要零扩展并复制到堆栈。alone push只能复制,而不能零扩展较窄的源,这就是为什么您需要对内存中的 1 字节变量进行额外指令的原因。


    – 

首先,push n1推送地址。 push dword [n1]从内存中加载 4 个字节并推送它们。YASM


msg: db ...紧随其后n1: db 2,因此 4 字节 (dword) 负载会将 3 个字节的 ASCII 字符作为整数的高字节。%d格式会将这 4 个字节全部作为int要打印的。

%#xx86 是小端字节序,因此如果您在格式字符串中使用,您会看到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 术语来说,采用intand %hd/%hhd转换会截断它,因为它是可变的,因此适用默认参数提升:窄整数类型提升为int。)

为你的全局保留 4 个字节

n1: dd 2允许从内存中push dword [n1]加载0x0000002,因此%d转换为打印整个内容int将仅打印2

用 C 语言static int n1 = 2;来说,这是static int8_t n1 = 2;