扫描一个字符串并在 64 位汇编气体中打印 strlen
scanf a string and print strlen in assembly gas 64-bit
我正在尝试使用 64 位 GAS 在汇编中编写一个 strlen
函数。
我需要从用户那里得到一个输入字符串,然后打印
它的长度。这是我的代码:
.lcomm d2, 255
.data
pstring1: .ascii "%s[=10=]\n"
.text
.globl main
main:
movq %rsp, %rbp
subq , %rsp
movq $d2, %rsi
movq %rsi,%rbx
movq $pstring1, %rdi
movq [=10=],%rax
call scanf
movq , %rax
movq $d2, %rsi
movq $pstring1, %rdi
call printf #print to check if scanf worked write
add , %rsp
movq 8(%rsp), %rcx
movq %rcx, d2
call pstrlen
popq %rbx
ret
##########
pstrlen:
movq %rsp, %rbx
movq 16(%rbp),%rdx
xor %rax, %rax
jmp if
then:
incq %rax
movq $length,%rax
if:
movq %rdx, %rcx
cmp 0, %rcx
jne then
end:
pop %rbp
ret
如果有人能举例说明如何在 64 位 GAS 程序集中使用字符串并将参数传递给函数,那将是理想的选择,因为我在网上找不到合适的东西。
原则上,您正在使用 .lcomm d2, 255
为字符串数据分配 255 个字节。一个字节是 8 位,1 位不是 0 就是 1。所以当作为无符号二进制值处理时,一个字节的最大值是 28-1。这对我来说是最常见的方式,我如何看待字节(作为数字 0..255
),但是这 8 位也可以表示其他值,例如有时使用带符号的 8 位(-128..+127
),或特定位被寻址,为访问它们的特定代码赋予它们特定的功能。 (这部分不错)
然后你使用 scanf
和 "%s[=15=]\n"
定义(它将编译为字节 '%', 's', 0, 10
...不确定空终止符之后的 10 有什么用处)。我会改用 .asciiz "%254s"
,以防止恶意用户向保留的 d2
space 输入超过 255 个字节的输入。 (注意它是 .asciiz
,最后是 z
,所以它会自己添加零字节)
那你就用printf
。而是单独为输出提供另一个格式化字符串,这次像 formatOut: .asciiz "%s\n"
.
终于要strlen
.
这意味着我将return返回输入。如果你是运行 in normal 64b OS (linux),你的输入字符串很可能是UTF-8编码的(除非你的OS设置在其他特定的Locale中,那么我不确定 scanf
选择哪个语言环境。
UTF-8编码是变长编码,所以你要决定你的strlen
是return字符数,还是占用字节数。
为简单起见,我假设字节数(不是字符数)对您来说已经足够了,如果您的输入字符串仅包含基本的 7b ASCII 字符([0-9A-Za-z !@#$%^&*,.;'\<>?:"|{}]
等...请检查任何 ASCII table ...不允许重音字符(如 á
),这将产生多字节 UTF8 代码),然后字节数也将等于字符数(UTF-8 编码有点像与 7b ASCII 兼容)。
这意味着例如对于输入 "Hell 1234"
,地址 d2
处的内存将包含这些值(十六进制)48 65 6C 6C 20 31 32 33 34 00
。再一次,如果你检查 ASCII table,你会意识到例如字节 0x20
是 space 字符,等等......而字符串是 "nul terminated",最后一个值零是字符串的一部分,但不显示,而是被各种 C 函数用作 "end of string marker".
所以你想在 strlen
中做的是用 d2
地址加载一些寄存器,比方说 rdi
。然后逐字节扫描(字节,因为ASCII编码是“1个字符=1个字节”的方式,我们将忽略UTF-8变长代码),直到内存中的值为零,同时统计有多少字节它确实需要达到它。如果你稍微思考一下这个想法,把它变成 "short" 换成 CPU,然后你会用 SCASB
来扫描(你也可以把它写成 "manually" 用普通的 mov/cmp/inc/jne/jnz
如果你愿意的话),你可以这样结束:
rdi = d2 address
rdx = rdi ; (copy of d2 address)
ecx = 255 ; maximum length of string
al = 0 ; value to test against
repne scasb ; repeat SCASB instruction until zero is found
; here rdi points at the zero byte
; (or it's d2+255 if the zero terminator is missing)
rdi -= rdx ; rdi = length of string
; return result as you wish
所以你首先需要正确理解你正在操作的值是什么,它们在哪里,它们的 bit/byte 大小是多少,它有什么结构。
然后您可以编写指令,根据这些数据进行任何合理的计算。
在你的情况下,计算是 "length_of_string = number of non-zero bytes in 7b ASCII encoded string stored in memory at address d2
"(我的意思是在成功 scanf
部分代码之后)。
考虑到你的源代码在我看来你不明白 x86 CPU 指令的作用,你只是从一些例子中复制它们。那会让你很快陷入困境。
例如 cmp 0, %rcx
正在检查 rcx
(8 字节 "wide" 值)是否等于零。你确实用 rdx
中的值加载了 rcx
,这是来自堆栈的东西(可能是 d2
地址),所以 rcx
永远不会为零。
即使你真的将内存中的字符值加载到 rcx
,你也会同时加载其中的 8 个,所以你会错过 0
值在一些垃圾中只有一个字节,比如 0xCCCCCCCC00343332
(我在 d2
缓冲区之后使用 0xCC
作为未定义的内存,例如,可能有任何值)。
因此该代码没有任何意义。如果您至少部分了解什么是 CPU 寄存器以及 mov/inc/cmp/...
等指令的作用,那么您就有机会通过简单地大量使用调试器来生成工作代码,以验证几乎每 1-2 条新指令添加到源代码,如果它确实操作了正确的值,并修复它们直到你做对了。
首先需要你清楚"correct behaviour"是什么! (就像在这种情况下 "fetching byte-by-byte values from d2
address, one after another, incrementing "length" 计数器,并寻找零字节)所以你可以判断代码何时执行你需要的操作。
我想通过这个答案指出的是,说明本身虽然重要,但不如您对 data/structures/algorithm 的看法重要。您的问题听起来好像您不知道 x86 程序集中的 "C string" 是什么,或者要使用哪种算法。这使您不可能只 "guess" 一些指令到源代码中,然后验证您是否猜对了。因为你不知道你想让它做什么。这就是为什么我告诉你还应该检查非 gas x86 Assembly 资源的最基本知识,什么是 bit/byte/computer memory/etc... 直到你稍微了解了哪些数值被操纵,例如创建"strings".
一旦你清楚它应该做什么,你就可以很容易地在调试器中捕捉到诸如交换参数之类的东西(例如:movq %rcx, d2
- 为什么你从 [=40 中放置 8 个字节=] into memory at address d2
? That will overwrite the input string), and similar, 所以你实际上不需要100%很好地理解指令和gas语法,只需要产生一些东西,然后结束"fix" 它的几次迭代。就像检查寄存器+内存视图一样,发现 rcx
并没有改变,而是字符串数据被损坏了 => 换个方式试试...
哦,我完全忘了...您需要查找 64b 平台 ABI 的文档,这样您就知道将参数传递给 C 函数的正确方法是什么。
例如 linux 这些教程可能会有所帮助:
http://cs.lmu.edu/~ray/notes/gasexamples/
并在此处搜索单词 "ABI" 以获取更多资源:
https://whosebug.com/tags/x86/info
我正在尝试使用 64 位 GAS 在汇编中编写一个 strlen
函数。
我需要从用户那里得到一个输入字符串,然后打印
它的长度。这是我的代码:
.lcomm d2, 255
.data
pstring1: .ascii "%s[=10=]\n"
.text
.globl main
main:
movq %rsp, %rbp
subq , %rsp
movq $d2, %rsi
movq %rsi,%rbx
movq $pstring1, %rdi
movq [=10=],%rax
call scanf
movq , %rax
movq $d2, %rsi
movq $pstring1, %rdi
call printf #print to check if scanf worked write
add , %rsp
movq 8(%rsp), %rcx
movq %rcx, d2
call pstrlen
popq %rbx
ret
##########
pstrlen:
movq %rsp, %rbx
movq 16(%rbp),%rdx
xor %rax, %rax
jmp if
then:
incq %rax
movq $length,%rax
if:
movq %rdx, %rcx
cmp 0, %rcx
jne then
end:
pop %rbp
ret
如果有人能举例说明如何在 64 位 GAS 程序集中使用字符串并将参数传递给函数,那将是理想的选择,因为我在网上找不到合适的东西。
原则上,您正在使用 .lcomm d2, 255
为字符串数据分配 255 个字节。一个字节是 8 位,1 位不是 0 就是 1。所以当作为无符号二进制值处理时,一个字节的最大值是 28-1。这对我来说是最常见的方式,我如何看待字节(作为数字 0..255
),但是这 8 位也可以表示其他值,例如有时使用带符号的 8 位(-128..+127
),或特定位被寻址,为访问它们的特定代码赋予它们特定的功能。 (这部分不错)
然后你使用 scanf
和 "%s[=15=]\n"
定义(它将编译为字节 '%', 's', 0, 10
...不确定空终止符之后的 10 有什么用处)。我会改用 .asciiz "%254s"
,以防止恶意用户向保留的 d2
space 输入超过 255 个字节的输入。 (注意它是 .asciiz
,最后是 z
,所以它会自己添加零字节)
那你就用printf
。而是单独为输出提供另一个格式化字符串,这次像 formatOut: .asciiz "%s\n"
.
终于要strlen
.
这意味着我将return返回输入。如果你是运行 in normal 64b OS (linux),你的输入字符串很可能是UTF-8编码的(除非你的OS设置在其他特定的Locale中,那么我不确定 scanf
选择哪个语言环境。
UTF-8编码是变长编码,所以你要决定你的strlen
是return字符数,还是占用字节数。
为简单起见,我假设字节数(不是字符数)对您来说已经足够了,如果您的输入字符串仅包含基本的 7b ASCII 字符([0-9A-Za-z !@#$%^&*,.;'\<>?:"|{}]
等...请检查任何 ASCII table ...不允许重音字符(如 á
),这将产生多字节 UTF8 代码),然后字节数也将等于字符数(UTF-8 编码有点像与 7b ASCII 兼容)。
这意味着例如对于输入 "Hell 1234"
,地址 d2
处的内存将包含这些值(十六进制)48 65 6C 6C 20 31 32 33 34 00
。再一次,如果你检查 ASCII table,你会意识到例如字节 0x20
是 space 字符,等等......而字符串是 "nul terminated",最后一个值零是字符串的一部分,但不显示,而是被各种 C 函数用作 "end of string marker".
所以你想在 strlen
中做的是用 d2
地址加载一些寄存器,比方说 rdi
。然后逐字节扫描(字节,因为ASCII编码是“1个字符=1个字节”的方式,我们将忽略UTF-8变长代码),直到内存中的值为零,同时统计有多少字节它确实需要达到它。如果你稍微思考一下这个想法,把它变成 "short" 换成 CPU,然后你会用 SCASB
来扫描(你也可以把它写成 "manually" 用普通的 mov/cmp/inc/jne/jnz
如果你愿意的话),你可以这样结束:
rdi = d2 address
rdx = rdi ; (copy of d2 address)
ecx = 255 ; maximum length of string
al = 0 ; value to test against
repne scasb ; repeat SCASB instruction until zero is found
; here rdi points at the zero byte
; (or it's d2+255 if the zero terminator is missing)
rdi -= rdx ; rdi = length of string
; return result as you wish
所以你首先需要正确理解你正在操作的值是什么,它们在哪里,它们的 bit/byte 大小是多少,它有什么结构。
然后您可以编写指令,根据这些数据进行任何合理的计算。
在你的情况下,计算是 "length_of_string = number of non-zero bytes in 7b ASCII encoded string stored in memory at address d2
"(我的意思是在成功 scanf
部分代码之后)。
考虑到你的源代码在我看来你不明白 x86 CPU 指令的作用,你只是从一些例子中复制它们。那会让你很快陷入困境。
例如 cmp 0, %rcx
正在检查 rcx
(8 字节 "wide" 值)是否等于零。你确实用 rdx
中的值加载了 rcx
,这是来自堆栈的东西(可能是 d2
地址),所以 rcx
永远不会为零。
即使你真的将内存中的字符值加载到 rcx
,你也会同时加载其中的 8 个,所以你会错过 0
值在一些垃圾中只有一个字节,比如 0xCCCCCCCC00343332
(我在 d2
缓冲区之后使用 0xCC
作为未定义的内存,例如,可能有任何值)。
因此该代码没有任何意义。如果您至少部分了解什么是 CPU 寄存器以及 mov/inc/cmp/...
等指令的作用,那么您就有机会通过简单地大量使用调试器来生成工作代码,以验证几乎每 1-2 条新指令添加到源代码,如果它确实操作了正确的值,并修复它们直到你做对了。
首先需要你清楚"correct behaviour"是什么! (就像在这种情况下 "fetching byte-by-byte values from d2
address, one after another, incrementing "length" 计数器,并寻找零字节)所以你可以判断代码何时执行你需要的操作。
我想通过这个答案指出的是,说明本身虽然重要,但不如您对 data/structures/algorithm 的看法重要。您的问题听起来好像您不知道 x86 程序集中的 "C string" 是什么,或者要使用哪种算法。这使您不可能只 "guess" 一些指令到源代码中,然后验证您是否猜对了。因为你不知道你想让它做什么。这就是为什么我告诉你还应该检查非 gas x86 Assembly 资源的最基本知识,什么是 bit/byte/computer memory/etc... 直到你稍微了解了哪些数值被操纵,例如创建"strings".
一旦你清楚它应该做什么,你就可以很容易地在调试器中捕捉到诸如交换参数之类的东西(例如:movq %rcx, d2
- 为什么你从 [=40 中放置 8 个字节=] into memory at address d2
? That will overwrite the input string), and similar, 所以你实际上不需要100%很好地理解指令和gas语法,只需要产生一些东西,然后结束"fix" 它的几次迭代。就像检查寄存器+内存视图一样,发现 rcx
并没有改变,而是字符串数据被损坏了 => 换个方式试试...
哦,我完全忘了...您需要查找 64b 平台 ABI 的文档,这样您就知道将参数传递给 C 函数的正确方法是什么。
例如 linux 这些教程可能会有所帮助: http://cs.lmu.edu/~ray/notes/gasexamples/
并在此处搜索单词 "ABI" 以获取更多资源: https://whosebug.com/tags/x86/info