表达式后的垃圾 `(0,1,1)'

junk `(0,1,1)' after expression

当我尝试 assemble 该程序时,我收到了以下错误消息:

misha@hp-laptop:~/test$ as -gstabs test.s -o test.o && ld test.o -o a.out && rm test.o && ./a.out
test.s: Assembler messages:
test.s:19: Error: junk `(0,0,1)' after expression
test.s:20: Error: junk `(0,1,1)' after expression
test.s:21: Error: junk `(0,2,1)' after expression
test.s:22: Error: junk `(0,3,1)' after expression

任何人都可以告诉我究竟我做错了什么我的程序不会 运行?显然,这与我试图访问每个长度为一个字节的数组元素的方式有关。这是程序本身:

/******************************************************************************
 *                                                                            *
 * This program prints the string "foo" on the console.                       *
 *                                                                            *
 ******************************************************************************/

.section .data
    array: .byte 0x00, 0x00, 0x00, 0x00  # An array of four bytes
    size:  .int  4                       # The size of the array


.section .text
.globl _start
_start:
    movb   [=11=]x66, %ah   # 66 is the hexadecimal value for 'f'
    movb   [=11=]x6F, %al   # 6F is the hexadecimal value for 'o'
    movb   [=11=]x6F, %bh   # 6F is the hexadecimal value for 'o'
    movb   [=11=]x0A, %bl   # A is the hexadecimal value for '\n'
    movb   %ah, array(0, 0, 1)
    movb   %al, array(0, 1, 1)
    movb   %bh, array(0, 2, 1)
    movb   %bl, array(0, 3, 1)

    # print
    movl   , %eax       # 4 is the number for the write system call
    movl   , %ebx       # The file descriptor to write to (1 - STDOUT)
    movl   $array, %ecx   # The starting address of the string to print
    movl   size, %edx     # The number of bytes to print
    int    [=11=]x80          # Wake up the kernel to run the write system call

    # exit
    movl   , %eax       # 1 is the number for the exit system call
    movl   [=11=], %ebx       # Exit status code (echo $?)
    int    [=11=]x80          # Wake up the kernel to run the exit system call

/*

Compile and run:

as -gstabs test.s -o test.o && \
ld test.o -o a.out && \
rm test.o && \
./a.out

*/

问题在于您如何引用数组成员。改用这个:

movb   %ah, array + 0
movb   %al, array + 1
movb   %bh, array + 2 
movb   %bl, array + 3

没有用于多维数组的 asm 语法,除非您自己使用宏构建它。或者,也许您通过在 (base, index, scale) 语法中用 0 替换未使用的寄存器来想出这个。

你可以做的是使用 expression involving a label 从中获取偏移量,例如 movb $constant, array + 4.

查看编译器输出通常是学习如何在 asm 中做事的好方法,从语法基础知识到巧妙的优化技巧。在 Godbolt compiler explorer:

#include <string.h>
char arr[100];   // uninitialized global array
void copy_string(){ memcpy(&arr[4], "foo\n", 4); }

    // -O3 -fverbose-asm output:
    movl    5075174, arr+4       #, MEM[(void *)&arr + 4B]
    ret

    .bss
    .align 32
arr:
    .zero   100

因此,arr+4 是语法。 我们可以编写 movl $const, arr+4(%eax) 来执行类似于 C 表达式 array[4 + i] 的操作。请参阅 for a complete list of x86 addressing modes (mostly NASM / MASM syntax, but what really matters is what's encodable in machine code.) See also the 标签 wiki。


还要注意 gcc 如何 puts uninitialized arrays in the .bss(而不是 .data.rodata)。这意味着您的可执行文件中没有一堆零字节。除了切换到带有 .bss 的部分,您还可以在任何地方使用 .comm array 100 来声明 array 并在 bss 中为其保留 100 个字节。只使用 .bss

可能不会那么令人困惑

那个直接常数当然是 0x0a6f6f66,我们的字符串。 gcc 巧妙地将 memcpy 优化为单个 4 字节的立即存储,因为它没有用处值(s)之后仍然在寄存器中。请记住,x86 是小端字节序的,因此 0x66 字节进入 array+0 并且 0x0a 进入 array+3。 (不过,gcc 在合并单独的狭窄存储方面很糟糕,而不是与 memcpy;请参阅神栓 link。clang 在这方面做得更好。)


在 NASM 语法中,您甚至可以用 "string" 作为整数常量来编写它。

mov    dword [array+off], `foo\n`    ;backquote for C-style \-escapes, unlike '' or ""

GNU as 不允许这样做,除非使用比十六进制常量更难阅读的内容 + 注释:

movl $('f' | 'o'<<8 | 'o'<<16 | '\n'<<24), array

GNU as 语法对于手写 asm 不如 NASM/YASM 友好,但在某些方面它很好。 (%reg 等等可以很容易地看出什么是寄存器名称,什么不是。)


说到立即数:你的size = 4应该是立即数,而不是负载

##  size:  .int  4   # Don't need this stored in memory anywhere
.equ size, 4
...

movl   $array, %ecx   # The starting address of the string to print
movl   $size, %edx    # The number of bytes to print

另请注意,movl $constant, (%ecx)movl $constant, array 占用更少的字节进行编码,因此您可以通过更快地将 $array 转换为 %ecx 来节省代码字节,然后使用简单的寄存器寻址模式。