装配和缓冲器 如何

Assembly and Buffers How to

我很困惑,遇到了障碍。 class 的作业要求我执行以下操作。

  1. 问候用户
  2. 输入提示
  3. 将字符串转换为全部大写
  4. 使用转换后的字符串向用户显示消息

我对 1 和 2 没有问题,需要时我可以找出将小写字母转换为大写字母的循环,例如;

cmp , %ah
jg Subtract
call Loop

Subtract:
    sub , %ah
    mov %ah, back to the array
    ret

这可能不是最好的方法,但一旦我弄清楚了这个数组和缓冲区,我就能弄清楚。所以教授让我们做事的方式涉及使用他的图书馆。为了获得用户输入,代码如下所示:

.data
Intro:
     .ascii "Hey enter in your what you want converted.\n[=11=]"
Task:
     .space 5  #This is the buffer that is supposed to limit what the user can enter.... I'm very confused about how to make this work

.text
.global _start

_start:

    mov $Intro, %rax
    call PrintCString #from the Prof's library
    mov $Task, %rbx
    call ScanCString

这是他对使用 ScanCString 的评价 输入 = rax, rbx Notes = 扫描以 null 结尾的字符串并将其存储到地址 %rax 中。寄存器 %rbx 必须包含可以读取的最大字符数(缓冲区的大小)。

我相信您可以从上面收集到的我的想法是移动每个字符,确定它是大写还是小写并相应地进行调整。我将 运行 通过一个循环,然后将其吐回给用户。

这是我到目前为止的所有内容,请不要介意我的测试内容太乱了。任何帮助将不胜感激。

.data
    Intro:
         .ascii "\nYup... Mr. Meekseeks here to help.  What ya want me to do?\n\n[=12=]"
    Task:
         .space 5
    NewLine:
         .ascii "\n\n[=12=]"
    Goodbye:
         .ascii "\nYou got it buddy I'll get right on doing [=12=]"
    Test:
         .ascii "\nYou made it to the first loop\n[=12=]"
    Test2:
         .ascii "\nYou made it past the first compare\n[=12=]"
    Test3:
         .ascii "\nYou added 1 to the pointer\n[=12=]"
    Test4:
         .ascii "\nHere's the string length [=12=]"

.text

.global _start

_start:

    mov $Intro, %rax                #start with greeting the user
    call PrintCString               #Print the greeting to the user
    mov [=12=], %rax
    mov Task(,8), %rbx              #move the buffer into RBX, prep for input
    call ScanCString                #User Input
#   mov %rax, Task
#   mov %rax, %rdx                  #Move the message so it's not destroyed
    call LengthCString              #Determine loop limit
    mov [=12=], %rdi                    #set pointer
    mov %rax, %rcx                  #set counter to zero
    mov Task(%rdi), %rax

    call PrintCString

    mov $Test4, %rax
    call PrintCString
    mov %rcx, %rax
    call PrintInt
    mov %rax, %rcx
    mov $Test, %rax
    call Loop

Loop:
    mov $Test2, %rax
    call PrintCString
    add , %rdi
    mov $Test3, %rax
    call PrintCString
    cmp %rdi, %rcx
    je Closing
    call Loop

#   movb %rax, %rdx
#   mov %rcx, %rax
#   call PrintInt
#   call PrintCString

#   call Ending

#Adding:

# Greeting:

#   mov $Intro, %rax
#   call PrintCString
#   ret

Closing:
    mov $Goodbye, %rax
    call PrintCString

    call Ending

Ending:

    mov $NewLine, %rax
    call PrintCString
    call EndProgram

ScanCString Input = rax, rbx Notes = Scans a null-terminated string and stores it into the address %rax. The register %rbx must contain the maximum number of characters that can be read (the size of the buffer).

看起来你应该这样称呼它:

    mov  $Task, %rax   # set rax to point to the buffer in memory
    mov  , %rbx      # size of buffer (5 bytes)
    call ScanCString

你的:

    mov [=11=], %rax

rax 设置为零(ScanCString 会将其用作内存指针,因此它将引用 "null" 指针并可能导致崩溃,或者如果它对空指针进行了安全测试则提前退出) .

    mov Task(,8), %rbx              #move the buffer into RBX, prep for input

从内存地址 Task 加载八个字节(实际上更可能是语法错误,我认为 (,8) 不会解析,但我不是 AT&T 语法专家......如果我愿意的话猜想它就像 Task(<noreg>,<noreg>,8),提供比例因子 8 是没有意义的,因为那里没有索引寄存器,所以逻辑上我会将其评估为零偏移量,添加到任务地址,并使用它来加载 8字节。

由于 Task: 后面跟着 .space 5,这意味着将从 space 保留中读取 5 个未定义的字节,然后读取接下来的 3 个字节以命中完整 8 个字节(rbx 大小)。

    call ScanCString                #User Input
#   mov %rax, Task

这会将 8 个字节的 rax 值存储到 Task: 指向的内存中(其中仅保留 5 个字节,因此它将覆盖超出的 3 个字节)。

#   mov %rax, %rdx                  #Move the message so it's not destroyed

rdx = rax ...不清楚 ScanCString return 在 rax 中的作用,所以谁知道 memory/rdx 中存储了什么以上两条说明。

    call LengthCString              #Determine loop limit

这可能需要指向 rax 中的内存指针,所以提前 mov $Task, %rax 对我来说似乎合乎逻辑,但你没有 post LengthCString 的文档.

...
jg Subtract
...

Subtract:
    ...
    ret

jg是跳转,没有把return地址存入栈,所以最后的ret感觉不对,ret需要对应的call 配对(除非你知道自己在做什么,并且你通过 call 以外的其他方式在堆栈中准备 returning 地址,但这不是初学者的主题)。

Task:
     .space 5  #This is the buffer that is supposed to limit
     # what the user can enter....
     # I'm very confused about how to make this work

不,这只是在当前代码段中保留了 5 个字节 (.data)。它不以任何方式限制代码,它甚至不定义该内存的任何内容,因此在执行时它可能包含任何内容(我认为 .data 通常在大多数平台上归零,因此它会在大多数目标平台上包含五个零字节(下次指定一个)。

5 只是在源代码中,它不是生成的机器代码的一部分,因此汇编器知道它,但代码本身不知道。如果你想 DRY,你应该定义一些像 buffer_length = 5 这样的常量(不确定 AT&T 语法),然后在数据部分 Task: .space buffer_length 和扫描调用 mov $buffer_length, %ebx 之前做(ebx 就足够了,因为你可能不会使用 4+GB 的长度,并且设置 ebx 部分只会清除 rbx 的剩余高 32 位),这样魔术常数 5 将仅保留在源代码中的一个位置。

总的来说,到目前为止,您似乎还没有完全理解什么是寄存器、什么是计算机内存、CPU 如何与它交互等等。尝试阅读一些有解释的书籍或教程,以及你的课程笔记。复制已经存在的代码并调整它,直到它产生你期望的输出,对于高级语言比汇编更好,在汇编中更好地尝试完全理解事情是如何工作的。好的部分是,计算机实际上是非常简单的机器,一个以确定性方式运行的状态自动机,每个周期只有很少的可能指令要执行,所以理解它并不难,只是不要使用人类expectations/logic 在上面,它是计算机器,而不是高级语言,应该是 written/read 人类和人类。

此外,在编写汇编代码时,必须始终验证每条指令在调试器中是否按预期工作,方法是单步执行它们,并检查指令产生的所有状态变化。如果出现一些差异,请查阅说明参考指南,以确保您完全理解特定说明的作用。如果您不想深入研究英特尔的原始 pdf(可从英特尔网站免费获得),网络上有一些简短版本,如 http://www.felixcloutier.com/x86/。不要根据它的名字来判断指令的作用,臭名昭著的例子(和关于 SO 的问题)例如 muldiv,它们根本不符合普遍的期望。

此外,AT&T 语法更像 IMO "machine-like",非常适合机器解析,而且非常 precise/exact,但与宽松的 Intel 语法相比,IMO 更难 write/read 随便.

尤其是像这样的东西:

mov  $Task, %rax   # set rax to point to the buffer in memory
mov  Task, %rax    # set rax to 64b value from memory at Task addres

对机器解析非常有意义, $ 区分立即值和内存引用,但是当你想要内存地址时,尽量不要忘记手写 $ ,并且不满足。