【问题标题】:Initialize array to specific values in assembly (x86)将数组初始化为程序集中的特定值(x86)
【发布时间】:2023-03-08 01:20:01
【问题描述】:

我想用特定的值在程序集中初始化一个数组。我首先尝试在循环中执行此操作,但在数组中出现了垃圾。然后我尝试手动操作并得到相同的垃圾。我希望数组重复 0 1 2 n 次。这是我尝试过的一些示例代码。

这是我手动加载数组的尝试。第一个值加载得很好。然而,当我在 GDB 中检查第二个值时,它会加载到垃圾中。

sub esp, 260
mov [ebp - 12], dword -1
mov [ebp - 16], byte 0
mov [ebp - 17], byte 1
mov [ebp - 18], byte 2
mov [ebp - 19], byte 0
mov [ebp - 20], byte 1
mov [ebp - 21], byte 2
mov [ebp - 22], byte 0
mov [ebp - 23], byte 1
mov [ebp - 24], byte 2
mov [ebp - 25], byte 0

这是我自动进行的尝试。

    sub esp, 260
    mov [ebp - 12], dword -1

again:
    add [ebp - 12], dword 1
    lea eax, [ebp - 16]
    sub eax, [ebp - 12]
    mov [eax], byte 0

    add [ebp - 12], dword 1
    lea eax, [ebp - 16]
    sub eax, [ebp - 12]
    mov [eax], byte 1

    add [ebp - 12], dword 1
    lea eax, [ebp - 16]
    sub eax, [ebp - 12]
    mov [eax], byte 2

    cmp [ebp - 12], dword 255
    jne again
    jmp elsewhere

使用 NASM、x86-32、Intel 语法。

编辑:当我将此代码转换为将数组值存储为 DWORD 而不是字节时,这两种方法都有效。这是为什么?

【问题讨论】:

  • 小贴士:用 C 语言编写您想要执行的操作并将编译器设置为输出程序集,查看编译器生成的内容并根据您的需要进行调整。
  • 它做的事情与我的手动方式非常相似。我会尝试模仿它的作用,但我看不出我的有什么不同。
  • 我无法逐行复制装配线,因为它由于某种原因无法编译。但无论如何我都会得到同样的垃圾。编辑当我将其转换为存储双字而不是字节时,它可以工作。这是为什么呢?
  • 为了消除混淆,我假设您的问题是如何初始化分配在堆栈上的数组?

标签: assembly x86 nasm


【解决方案1】:

使用 NASM,您可以使用 times 前缀轻松初始化重复数据。例如,要按照您的问题要求将序列“0 1 2”重复 n 次,您可以执行类似以下操作:

section .data

    my_array: times n db 0, 1, 2 

只需将n 替换为您想要的常量值。有关times 前缀的更多信息,请参见NASM Manual

【讨论】:

  • 在这种情况下,OPs 数组在堆栈上,因此您的建议不适用。
  • @MichaelPetch:实际上,如果我没记错的话,我认为 OP 并没有声明堆栈上的数组是必需的,他只是想“初始化一个数组”。即使他的尝试依赖于堆栈,他也可能想要以不同的方式做事,并采用我的回答建议的简单方法。 ;-)
  • 我意识到他在他的问题中所说的并且从未提及堆栈,但是如果您查看他的代码,您会看到他为数组分配了一个堆栈,然后尝试将其填充.您的答案不适用于堆栈,因为它在内存中没有可以使用times 指令的固定位置。在堆栈上初始化数组需要编程方法。
【解决方案2】:

假设您希望字节数组占用从 [EBP-260][EBP-16] 的堆栈空间,它将容纳 260 - 16 + 1 = 245 个字节。查看您的示例,我发现最后一个元素是 0。考虑到元素的数量不能被 3 整除,我可以计算出第一个元素需要是 1

(1, 0) followed by 81 times (2, 1, 0)

从循环中取出前两个元素创造了一个机会,可以用几个嵌套循环编写一个有效的解决方案:

  push ebp
  mov  ebp, esp
  sub  esp, 260

  ; Initializing the array with 245 bytes (2 + 81 * 3)
  mov  edx, esp
  mov  eax, 1
  mov  [edx], ax         ; (1, 0)
  inc  eax               ; 1 -> 2
  add  edx, eax
  mov  ecx, 81           ; 81 triplets (2, 1, 0)
Outer:
Inner:
  mov  [edx], al
  inc  edx
  dec  eax               ; 2 -> 1 -> 0 -> -1
  jns  Inner
  add  eax, 3            ; -1 -> 2
  dec  ecx
  jnz  Outer
  mov  byte [edx], -1    ; (*)

但如果我们一次写入 32 位,我们可以做得更好。内存中的数组呈现为:

| EBP-260 == ESP                                      | EBP-20    | EBP-16
v                                                     v           v
1, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0, 2, 1, ... , 1, 0, 2, 1, 0
\--------/  \--------/  \--------/  ^               ^ \--------/  -
    EAX         EBX         ECX     | repeats       |     EAX     BL
                                    | from here on  |     left-overs

在 245 字节的数组中,这 3 个 dword(12 字节)可以重复 20 次。而且因为数组开始的地址(ESP)是双字对齐的,所以我会把重复放在低端。

  push ebp
  mov  ebp, esp
  sub  esp, 260
  mov  byte [ebp-15], -1 ; (*)

  ; Initializing the array with 245 bytes (20 * 12 + 5)

  mov  eax, 01020001h    ; (1, 0, 2, 1)
  mov  ebx, 00010200h    ; (0, 2, 1, 0)
  mov  ecx, 02000102h    ; (2, 1, 0, 2)
  lea  edx, [ebp-32]
  mov  [ebp-16], bl
  mov  [ebp-20], eax
More:
  mov  [edx+8], ecx
  mov  [edx+4], ebx
  mov  [edx], eax
  sub  edx, 12
  cmp  edx, esp
  jae  More

(*) 如果这个 -1 的预期用途是终止列表,那么在[ebp-15] 使用 byte -1 比使用 dword -1[ebp-12]

【讨论】:

  • 紧凑且相当不错(尽管我会使用 EDX 作为指针并单独保留 EBP),但 IDK 关于“高效”。重复模式为 3 个字节长,您可以方便地一次存储 4 个字节。两个选项是未对齐和重叠 dword 存储(每次迭代 3 个有用字节),或者将模式展开为 LCM(3,4) = 12 个字节 = 3 个寄存器(或 3 个mov-immediate),以此作为非12 的倍数。或者只是剥离/展开清理作为一对可能重叠的mov 立即,或者在这种情况下是 4 字节 + 1 字节 mov。
  • 或使用fild/fist 或 MMX 一次存储 8 个字节,例如可能 8/7 重叠存储来执行 5x 3 模式(或 SSE 移动)。或者混合 8 和 4 字节对齐的存储来做 12 组的 2 存储而不是 3 存储。或展开到 LCM(3,8) 或 LCM(3,16) = 48 以使用对齐的 movap。
  • @PeterCordes 你相信我会添加这个吗?昨天我一登出,就想出了存储 3 个唯一 dwords 并重复它们的想法。在我写“高效”的地方,我主要是在考虑其他答案如何设法使用 division 来解决这个简单的任务。这也是我接手回答这个 4 年前的问题的唯一原因。
  • 是的,令当时甚至没有人提到更好的解决方案感到惊讶,甚至没有像16-bit FizzBuzz in x86 NASM assembly 这样的向下计数器。可以肯定的是,我之前已经看到过重复的 3 字节模式(可能是像素)的问答,所以这不是新的,但绝对值得一提,会再次 +1。 (顺便说一句,英文用法说明:“took it upon myself”是您要查找的成语/短语。)
【解决方案3】:

这可能会引发更多关于在堆栈上分配字节或双字的想法。

您可能想要构建一个您可能经常使用的个人函数或宏库,或者找到一些其他库来 %include。

您可以以字节、双字或其他大小来解释数组数据,就像您以相同的方式对待所有数据元素一样。您必须始终注意数组元素的大小(例如,1 对 4 字节..)。我会将任何对齐问题留给您解决。

section .data
        _len equ 64
section .text
        %macro mod 2
        mov eax, %1
        mov ebx, %2
        xor edx, edx
        div ebx                 ; div/mul are very slow; but ok for small loops
        mov eax, edx            ; return eax if you wish
        %endmacro

        global _start
_start:
        nop

        sub esp, _len                   ; 

        mov ecx, 1
        L1:

        mod ecx, 3                      ; n mod 3 
        mov byte [esp+ecx], al

        inc ecx
        cmp ecx, _len                   ; array size
        jb L1

        add sp, _len

_exit:
        mov eax, 1
        mov ebx, 0
        int 0x80

GDB 示例 x/64d $esp 使用 next....

(gdb) 
0xffffd180:     0       1       2       0       1       2       0       1
0xffffd188:     2       0       1       2       0       1       2       0
0xffffd190:     1       2       0       1       2       0       1       2
0xffffd198:     0       1       2       0       1       2       0       1
0xffffd1a0:     2       0       1       2       0       1       2       0
0xffffd1a8:     1       2       0       1       2       0       1       2
0xffffd1b0:     0       1       2       0       1       2       0       1
0xffffd1b8:     2       0       1       2       0       1       0       0
31      v eax, 1
(gdb) 
0xffffd180:     0       1       2       0       1       2       0       1
0xffffd188:     2       0       1       2       0       1       2       0
0xffffd190:     1       2       0       1       2       0       1       2
0xffffd198:     0       1       2       0       1       2       0       1
0xffffd1a0:     2       0       1       2       0       1       2       0
0xffffd1a8:     1       2       0       1       2       0       1       2
0xffffd1b0:     0       1       2       0       1       2       0       1
0xffffd1b8:     2       0       1       2       0       1       0       0
25      cx, 3                   ; n mod 3 
(gdb) 
0xffffd180:     0       1       2       0       1       2       0       1
0xffffd188:     2       0       1       2       0       1       2       0
0xffffd190:     1       2       0       1       2       0       1       2
0xffffd198:     0       1       2       0       1       2       0       1
0xffffd1a0:     2       0       1       2       0       1       2       0
0xffffd1a8:     1       2       0       1       2       0       1       2
0xffffd1b0:     0       1       2       0       1       2       0       1
0xffffd1b8:     2       0       1       2       0       1       0       0
27      cx
(gdb) 
0xffffd180:     0       1       2       0       1       2       0       1
0xffffd188:     2       0       1       2       0       1       2       0
0xffffd190:     1       2       0       1       2       0       1       2
0xffffd198:     0       1       2       0       1       2       0       1
0xffffd1a0:     2       0       1       2       0       1       2       0
0xffffd1a8:     1       2       0       1       2       0       1       2
0xffffd1b0:     0       1       2       0       1       2 

  0   

【讨论】:

    【解决方案4】:

    你可以使用 gas(at&t 语法)

    array:
      .space size, value
    

    您也可以使用 .skip,它与 .space 相同。

    【讨论】:

      【解决方案5】:

      这是我得到的:

      ;ecx = how much values  ;ds:edi = destination
      function:
      
          pushad                    ;push all 32bit registers to stack
      
          xor eax,eax               ;clear eax
      
          function_loop:
      
              push eax              
              mov ebx,0x00000003    ;we divide by 3
      
              xor edx,edx           ;clear edx 
      
              div ebx               ;this stores the remainder of eax/0x03 in edx
              mov DWORD [edi],edx   ;move it to your array
      
              pop eax               ;we dont need the result of the division
      
              inc eax 
              add edi,4             ;increment pointer 4 bytes because 4 bytes are one DWORD
      
          loop function_loop        ;loop til ecx is 0
      
          popad                     ;pop all 32bit registers
      ret
      

      我是凭脑子写的,所以请报告它的任何错误

      但你要做的就是增加一个寄存器并在递增寄存器上用 3 进行模运算,然后你就有 0,1,2 模式

      只要确保目标上有足够的空间来存储所有 n 值

      【讨论】:

      猜你喜欢
      • 2021-11-27
      • 1970-01-01
      • 1970-01-01
      • 2018-10-06
      • 2015-09-26
      • 2021-01-19
      • 2017-09-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多