如何在汇编过程中使用缓冲区?

7

我理解缓冲区的一般抽象概念:它是在处理数据之前在内存中分配的一个容器。我正在尝试完成一项作业问题,该问题要求我在过程中将ASCII字符串写入缓冲区。因此,我理解当调用该过程时,我应该传递一个数组地址给过程,例如...

main PROC
mov  EAX, packed           ; pass a packed decimal to EAX
mov  ESI, OFFSET ascArray  ; pass the offset of an empty array to ESI
call PackedToAsc           ; call the function

所以该函数应返回“指向带有ASCII十进制字符串的缓冲区的指针”。我可能有点愚蠢,在这种情况下,不太清楚缓冲区究竟是什么。

  • 它是一个数组吗?
  • 我需要在.data部分中声明它吗?
  • 如何在过程内声明指向ASCII字符串的指针?
  • 在这个上下文中,缓冲区是什么意思?

更实际地说,当该过程完成时,我需要访问数据被放入的缓冲区,但我不确定如何做到这一点。

编辑 -- 我在x86上使用MASM。

3个回答

5

是的,缓冲区就是一个数组,在汇编语言中它是一系列字节的序列。

你有三个主要选项来分配它,就像在C语言中一样:

  • static storage: like C static char buf[100];

      section .bss                   ; this might not be proper MASM syntax
       my_buffer:  db   100 dup(?)   ; but this is definitely MASM
    

    Putting a : between the label name and the db makes it just a plain label, like NASM, not a MASM "variable" with an implied operand-size. (If MASM lets you do that in a .data / .bss section. It might not.)

    100 dup means to repeat the next thing 100 times. ? means uninitialized storage. It's actually going to be zeroed in a program that runs under an OS like Windows, because it can't let programs see stale data left over from kernel data or other processes on the same machine. So 100 dup(0) would also work and maybe be a better description of what you want, especially if your code ever reads any of these bytes without writing first.

  • dynamic storage: call malloc, or invoke an OS function directly like mmap or VirtualAlloc. You can return a pointer to it from the function that allocated it.

  • automatic storage (on the stack): like a C local variable. Deallocated automatically when the allocating function returns. Very cheap and easy, use this for scratch buffers unless you know they need to be multiple megabytes.

处理缓冲区最简单的方法是接受一个已分配的缓冲区指针,并让调用者选择要传递的缓冲区。
例如,一个将ASCII字母大写的函数可以只采用src和dst指针。如果您希望它在原地操作,只需为输入和输出传递相同的指针(如果编写时支持),它就不必关心内存管理,只是在两个缓冲区之间操作。
像C中的strdup这样的函数会创建字符串的新副本,只有使用动态存储才有意义。将字符串复制到静态缓冲区并返回它不起作用,因为只有一个该静态缓冲区的实例。下一次调用它将覆盖旧内容。
在堆栈上分配缓冲区: 在堆栈上分配可变大小的缓冲区不是问题;您只需要一种清理堆栈的方法。使用 EBP/RBP 创建一个堆栈帧是一种简单的方法。考虑这个示例函数,它分配一个足够大的缓冲区,并使用它来保存字符串反转函数的输出,以便将其传递给 print 函数。您可以查看
void string_reverse(char *d, const char*s, int len);
void print(const char*s, int len);  // modify this to an fwrite or whatever.

void print_reversed(const char *s, int len) {
    char buf[len];
    string_reverse(buf, s, len);
    print(buf, len);
}

如果 string_reverse 不需要16字节的栈对齐,并且它不会破坏其栈参数,那么您可能会手动执行此操作。 (ABI /调用约定没有保证这两个事情,因此我们利用调用的特殊知识来简化print_reversed)。

; MSVC __fastcall convention
; args: ecx, edx    (const char *string,  size_t length)
print_reversed PROC
    push   ebp
    mov    ebp, esp         ; make a stack frame

    sub    esp, edx         ; reserve space for a buffer
    and    esp, -16         ; and realign the stack
    ; allocate buf[length] on the stack, address = esp
      ; mov eax, esp       ; if you want to copy it somewhere

        ;sub    esp, 12          ; ensure 16-byte stack alignment before CALL

    push   edx              ; 3rd arg and later args go on the stack
    mov    edx, ecx         ; 2nd arg = string
    lea    ecx, [esp+4]     ; 1st arg = output buffer = what we allocated.  (PUSH offset ESP by 4, LEA corrects for that)

    call    string_reverse   ; (dst=buf (ECX),  src=string (EDX), length=length (stack))
      ; clean up the stack after the call and set up args for print
    pop    edx              ; assuming string_reverse doesn't modify its stack arg
    mov    ecx, esp         ; esp is once again pointing to our buffer
    call   print            ; print(ECX=buf, EDX=length)

      ; lea     esp, [ebp-8]  ; if you needed to push stuff after EBP, restore this way
      ; pop  ebx  / pop esi / pop ebp
    
    leave                   ; mov esp, ebp / pop ebp to clean up the stack frame
    ret
ENDP

这是大多数C编译器实现alloca或C99可变长度数组的方式。

更正:32位的fastcall是一种被调用者弹出参数的约定,在调用后,控制权重新回到调用者时,栈上的参数已经被移除。因此,我们需要将传入的EDX参数保存在某个地方,而不是使用pop edx来恢复原始的size_t len。(例如,在分配VLA之前将其推入栈中,这样它就位于[ebp-4]处。) - Peter Cordes

4
假设使用x86架构,这取决于你的缓冲区是否带有数据以及其大小是否可变。
对于情况一,如果你确信你的缓冲区永远不会超过20个字节,你可以在数据段(NASM语法)中声明它。
buffer: times 20 db 0

在数据段声明了20个0字节,您现在可以使用它们。如果您不需要使用数据进行初始化,则可以使用.bss部分(NASM语法):
buffer: resb 20

这条命令告诉NASM预留20个字节。

然而,如果您的缓冲区大小是可变的,情况就不那么容易了。您必须从操作系统中动态分配内存,这非常依赖于操作系统。基本上有两个选择:

  • 使用C库的malloc: 这可能更容易或更难,具体取决于平台调用约定。这里有一个合理的参考链接
  • 使用系统调用:所有系统都提供一种获取更多内存的方法,但它们都不像malloc那样美观或易用。它们通常涉及向进程添加另一个内存页面并让您自己管理它。这就是malloc在内部执行的操作,并且是唯一的选择如果您不能使用C库。

2
在堆栈上分配缓冲区就像 sub esp, ecx / and esp, -16 一样简单。这是为本地使用或传递给其他函数的临时缓冲区的最佳选择。 - Peter Cordes
请注意,此答案完全使用NASM语法;在发布此答案后,问题已更新为指定MASM :( 概念是正确的,但是在MASM中,两种语法结构都不起作用。 - Peter Cordes

-2

我使用这个方法来获取缓冲区中的字符并与其他字符串进行比较

CompareText macro colorScreen,string1,ScoreofLabel,loop_1,endText,notText,backprint

    mov h3,cx
    ClescrColor colorScreen                                     
    mov cx,h3

    printstr string1

    lea SI,string1[0]
    loop_1:       
        mov AH,01 
        int 16H
        jz backprint

    mov AH,00      
    int 16H       
    mov AH, [SI]   
    inc SI    

    cmp ah,36
        jne endText
    mov ah,13           

    endText:
    cmp AL, AH
        jne notText
    cmp AL, 13    
        jne loop_1


    jmp ScoreofLabel
    notText:
        mov si,62


endm 

希望能够帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接