我正在尝试使用64位GAS在汇编中编写strlen
函数。
我需要从用户那里获取输入字符串并打印
它的长度。这是我的代码:
.lcomm d2, 255
.data
pstring1: .ascii "%s\0\n"
.text
.globl main
main:
movq %rsp, %rbp
subq $8, %rsp
movq $d2, %rsi
movq %rsi,%rbx
movq $pstring1, %rdi
movq $0,%rax
call scanf
movq $1, %rax
movq $d2, %rsi
movq $pstring1, %rdi
call printf #print to check if scanf worked write
add $8, %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汇编中的函数,那将是理想的,因为我找不到任何合适的在线。
答案 0 :(得分:0)
在原则级别,您使用.lcomm d2, 255
为字符串数据分配255个字节。一个字节是8位,1位是0或1.因此当被视为无符号二进制值时,一个字节的最大值是2 8 -1。这对我来说是最常见的方式,我如何考虑字节(作为数字0..255
),但这8位也可以表示其他值,例如有时使用带符号的8位(-128..+127
),或特定位被寻址,为访问它们的特定代码提供特定功能。 (这部分很好)
然后你使用带有scanf
定义的"%s\0\n"
(它将编译为字节'%', 's', 0, 10
...不确定在null终止符之后10对那里有什么好处)。我会使用.asciiz "%254s"
代替,以防止恶意用户将更多255字节的输入输入到保留的d2
空间。 (请注意.asciiz
z
的结尾,因此它会在其上添加零字节(
然后使用printf
。而是分别为输出提供另一个格式化字符串,这次是formatOut: .asciiz "%s\n"
。
最后你需要strlen
。
这意味着我将返回输入。如果你在普通的64b OS(linux)中运行,你的输入字符串很可能是UTF-8编码的(除非你的OS设置在其他特定的Locale中,那么我不确定哪个Locale会scanf
选择向上)。
UTF-8编码是可变长度编码,因此您应该决定strlen
是否会返回字符数或占用的字节数。
为了简单起见,我假设字节数(不是字符)对你来说已经足够了,如果你的输入字符串只包含基本的7b ASCII字符([0-9A-Za-z !@#$%^&*,.;'\<>?:"|{}]
等...请检查任何ASCII表..允许没有重音字符(如á
),这将产生多字节UTF8代码),然后字节数也将等于字符数(UTF-8编码与7b ASCII兼容)。
这意味着例如对于输入"Hell 1234"
,地址d2
处的内存将包含这些值(十六进制)48 65 6C 6C 20 31 32 33 34 00
。再次,如果您将检查ASCII表,您将意识到例如字节0x20
是空格字符等...并且字符串是&#34; nul终止&#34;,最后一个值零是字符串的一部分,但不显示,而是被各种C函数用作&#34;字符串标记的结尾&#34;。
所以你要在strlen
中做的是加载一些d2
地址的注册表,让我们说rdi
。然后逐字节扫描(字节,因为ASCII编码工作在&#34; 1 char = 1字节&#34;方式,我们将忽略UTF-8可变长度代码),直到你在内存中达到零值,并且同时计算它到达它需要多少字节。如果你想对这个想法进行一些思考,那就让它变得简短。对于CPU,您将使用SCASB
进行扫描(如果您愿意,还可以使用{#1;手动&#34;使用普通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
因此,您需要首先正确理解您正在操作的值,它们的位置,位/字节大小以及它具有的结构。
然后你可以编写基于这些数据产生任何合理计算的指令。
在您的情况下,计算是&#34; length_of_string =存储在地址d2
&#34;的内存中的7b ASCII编码字符串中的非零字节数。 (我的意思是在成功scanf
部分代码之后。)
考虑到你的源代码看起来,它看起来像你不了解x86 CPU指令的作用,你只需从一些例子中复制它们。那很快就会让你陷入困境。
例如cmp 0, %rcx
正在检查rcx
(8字节&#34;宽和#34;值)是否等于零。并且你确实加载了来自rcx
的值为rdx
的{{1}},这是来自堆栈的内容(可能是d2
地址),因此rcx
将永远不会为零。
即使您实际上将内存中的字符值加载到rcx
,您也会同时加载其中的8个,因此您会错过0
值,因为它只会是单个一些垃圾中的字节,例如0xCCCCCCCC00343332
(例如,0xCC
缓冲区后面的未定义内存使用d2
,可能有任何值。)
因此代码没有任何意义。如果您至少部分了解什么是CPU寄存器以及mov/inc/cmp/...
之类的指令,那么您有机会通过简单地使用调试器来生成工作代码,以验证几乎每1-2个新指令添加到源,如果它确实操纵了正确的值,并修复它们直到你做对了。
这要求您清楚地了解&#34;正确的行为&#34;第一! (就像在这种情况下&#34;从d2
地址一个接一个地获取逐字节值,递增&#34;长度&#34;计数器,并寻找零字节)所以你可以告诉我什么时候代码可以满足您的需要。
我想用这个答案指出的是,指令本身虽然重要,但并不像你对数据/结构/算法的看法那么重要。你的问题听起来像你不知道什么是&#34; C string&#34;在x86程序集中,或使用哪种算法。这使你不可能只是猜测&#34;一些指令进入源,然后验证你猜对了。因为你不能说出你想要它做什么。这就是为什么我告诉你应该检查非气体x86组件资源的基础知识,什么是位/字节/计算机内存/等......直到你有点理解操作的数值是什么,例如创建&#34;串&#34;
一旦你知道它应该做什么,你就很容易在调试器中捕获交换参数(例如:movq %rcx, d2
- 为什么要从rcx
中放入8个字节在地址d2
的内存中?这将覆盖输入字符串),类似的,所以你实际上不需要100%理解指令和气体语法,只需要产生一些东西,然后结束几次迭代到&#34;修复&#34;它。就像检查寄存器+内存视图一样,实现rcx
没有改变,而是字符串数据被损坏=&gt;另外尝试一下......
哦,我完全忘记了......你需要找到64b平台ABI的文档,所以你知道将参数传递给C函数的正确方法是什么。
例如在linux中,这些教程可能有所帮助: http://cs.lmu.edu/~ray/notes/gasexamples/
在这里搜索单词&#34; ABI&#34;了解更多资源: https://stackoverflow.com/tags/x86/info