san*_*yio 6 assembly buffer procedure
所以,我理解缓冲区的一般抽象概念:它是内存中的一种分配,用于在数据被处理之前保存数据。我正在尝试完成一个作业问题,该问题要求我将 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
Run Code Online (Sandbox Code Playgroud)
因此该函数应该返回“一个指向带有 ASCII 十进制字符串的缓冲区的指针”。我可能在这里很愚蠢,但我不太清楚在这种情况下缓冲区究竟是什么。是数组吗?我需要在 .data 部分声明它吗?如何在过程中声明指向 ASCII 字符串的指针?在这种情况下缓冲区是什么意思?更实际的是,我需要访问程序完成时数据放入的缓冲区,但我不知道该怎么做。对不起,如果我在这里不够清楚......让我知道我如何澄清这个问题。
编辑 ——我在 x86 中,我正在使用 MASM。
是的,缓冲区只是一个数组,在汇编中它是一个字节序列。
您有 3 个主要选项来分配它,就像在 C 中一样:
静态存储:如Cstatic char buf[100];
section .bss ; this might not be proper MASM syntax
my_buffer: db 100 dup(?) ; but this is definitely MASM
Run Code Online (Sandbox Code Playgroud)
:在标签名称和 之间放置 adb使其只是一个普通标签,如 NASM,而不是具有隐含操作数大小的 MASM“变量”。(如果 MASM 允许您在 .data / .bss 部分中执行此操作。它可能不会。)
100 dup意思是把接下来的事情重复100次。 ?表示未初始化的存储。实际上,在 Windows 等操作系统下运行的程序中,它会被清零,因为它不能让程序看到内核数据或同一台机器上的其他进程留下的陈旧数据。所以100 dup(0)也可以工作,并且可能是对您想要的内容的更好描述,特别是如果您的代码在不先写入的情况下读取了这些字节中的任何一个。
动态存储:call malloc,或直接调用操作系统函数,如mmap或VirtualAlloc。您可以从分配它的函数返回指向它的指针。
自动存储(在堆栈上):像 C 局部变量一样。当分配函数返回时自动释放。非常便宜且简单,可以将其用于暂存缓冲区,除非您知道它们需要多个兆字节。
处理缓冲区的最简单方法是接受指向已分配缓冲区的指针,并让调用者选择要传递的缓冲区。
例如,大写 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);
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
这就是大多数 C 编译器实现allocaC99 变长数组的方式。
假设是 x86,这取决于您的缓冲区是否以其中的数据开始。它还取决于您的缓冲区大小是否可变。
以情况一为例,您知道缓冲区永远不会超过 20 个字节,您可以在数据部分中声明它(NASM 语法):
buffer: times 20 db 0
Run Code Online (Sandbox Code Playgroud)
在数据部分声明了0您现在可以使用的 20 个字节。如果不需要使用数据进行初始化,则可以使用 .bss 部分(NASM 语法):
buffer: resb 20
Run Code Online (Sandbox Code Playgroud)
这告诉 NASM 保留 20 个字节。
然而,如果缓冲区的大小是可变的,事情就不那么容易了。您必须从操作系统动态分配内存,这非常依赖于操作系统。你基本上有两个选择:
malloc:这可能更容易或更难,具体取决于平台调用约定。这里有一个合理的参考malloc. 它们通常涉及向您的进程添加另一页内存并让您自己管理它。这是 malloc 在内部执行的操作,如果您无法使用 C 库,这是唯一的选择。