【问题标题】:How to compare Command Line Arguments (x86_64)如何比较命令行参数 (x86_64)
【发布时间】:2022-10-23 18:42:41
【问题描述】:

我正在处理 Assembly GAS/AT&T x86_64 任务,这需要我们获取一些命令行参数并使用它们进行一些操作。

我已经弄清楚了它们在堆栈中的位置,但是我不知道如何将参数的内容与另一个字符串进行比较,以检测用户是否输入了特定的参数。这是我正在尝试做的一个最小示例。但是,执行永远不会到达he 子例程。

.text

output: .asciz "%s"

arg: .ascii "-i"

.global main

main:

movq 8(%rsi), %rsi

movq arg, %rdi

cmpq %rsi, %rdi
je he

movq    $0, %rdi            
call    exit                

he:

movq $output, %rdi

movq $0, %rax
call printf

movq    $0, %rdi            
call    exit                

我究竟做错了什么?提前感谢您的帮助!

【问题讨论】:

  • 使用strcmp 函数比较字符串。或者编写一个循环来逐个字符地比较它们。仅比较指针只会告诉您是否都指向同一个字符串(不是两个具有相同内容的不同字符串)。
  • 使用调试器查看寄存器内容,注意mov 8(%rsi), %rsi 加载的8 个字节是一个指针argv[1]。获取一些 ASCII 字节将需要另一个取消引用。你基本上是在做memcmp(&argv[1], "-i", 8)。哦,还有,你的"-i" 字符串后面直接跟main 的机器代码,因为你没有把它放在像.rodata 这样的不同部分的末尾。也许您希望 cmpw $('-'<<8) | 'i', (%rsi) 比较 2 个字节(不包括终止 0)。不幸的是,与 NASM 不同,GAS 不喜欢使用多字符文字作为数字文字。
  • 您可以只编译一个执行 memcmp(argv[1], "-i", 2) 的 C 程序,然后查看编译器如何在启用优化的情况下执行此操作。
  • (或者当然看看它是如何内联strcmp,如果你想要检查一个完整的字符串,而不是仅仅从这 2 个字节开始。)godbolt.org 对于查看 GCC asm 输出很有用。使用-O3 或至少-O2
  • @PeterCordes 感谢您的回复...但是,我似乎仍然无法使其工作...我添加了另一行 movq (%rsi), %rsi 以进行另一次取消引用,然后我在之后声明了 5 个字节 .byte 0x00 arg: .ascii "-i" 声明,但执行仍然无法到达he 子程序...

标签: assembly x86-64 att


【解决方案1】:

您正在将指针与 arg 字符串的 8 个字节进行比较。

要比较字符串,由于您使用的是 C 运行时,您可以像在 C: 中那样使用strcmp 进行比较。

.global main
.text

 strIArg: .asciz "-i"
 strHello: .asciz "Hello.
"

main:
 sub $8, %rsp
 
 #At least two args?
 cmp $2, %edi
 jb 1f

 #2nd arg is equal to strIArg?
 mov 8(%rsi), %rdi           #argv[1] in RDI
 lea strIArg(%rip), %rsi     #"-i" in RSI (I'm making a PIE, hence the RIP-relative addressing)
 call strcmp
 
 test %eax, %eax            #strcmp returns 0 if the two strings are equal
 jnz 1f
 
 #OK, arg found
 
 lea strHello(%rip), %rdi
 call printf

1:
 xor %edi, %edi
 call exit

或者,如果您的参数足够短并且您的程序非常简单,您可以减少对strcmp 的调用以提高性能。

.global main

.text

strHello: .asciz "Hello.
"

main:
 sub $8, %rsp
 
 #At least two args?
 cmp $2, %edi
 jb 1f

 #2nd arg is equal to strIArg?
 mov 8(%rsi), %rdi
 
 cmpb $0, (%rdi)
 je 1f          #Empty string?
 
 cmpw $0x692d, (%rdi)   #Starts with -i ?
 jne 1f
 
 cmpb $0, 2(%rdi)   #And then it ends?
 jne 1f
 
 #OK, arg found
 
 lea strHello(%rip), %rdi
 call printf

1:
 xor %edi, %edi
 call exit

但我不会推荐它,除非是在最简单的情况下,因为 GAS doesn't support string literals as immediates 并且您需要自己转换字符串(注意 x86 的小端序),这会降低代码的可读性。

最后,对于在 POSIX 系统上运行的更复杂的程序,您可能需要考虑getopt_long and variants
下面是一个程序示例,它欢迎在命令行上传递的名称,并采用两个可选参数来修改其行为。
请注意getopt_long 将如何处理参数的重新排序、处理极端情况(例如,当用户将-un X 作为-u -n X 的缩写传递时)以及为我们处理--

.global main

.data

 #Name to use for the greetings
 name: .quad defaultName
 
 #Greeting string to use
 greetings: .quad strHello

 #The long options accepted
 
 nameOpt: 
    .quad nameOptName   #name
    .quad 1         #has arg
    .quad 0         #ptr to flag to update with val (0 to make getopt_long return val instead)
    .quad 'n'       #val
 uppercaseOpt: 
    .quad uppercaseOptName
    .quad 0
    .quad 0
    .quad 'u'
 nullOpt:
    .quad 0 
    .quad 0
    .quad 0
    .quad 0     #Last option must be null

.text

 #Greetings strings
 strHello: .asciz "Hello %s from %s!
"
 strHelloUpper: .asciz "HELLO %s FROM %s!
"
 
 #Default name
 defaultName: .asciz "Margaret"


 
 nameOptName: .asciz "name"
 uppercaseOptName: .asciz "upper"
 
 #The short options accepted, note how we use "n" and "u" for both the long and short options
 #this is to reuse the logic but getopt_long allows to distinguish the two cases
 
 shortOpts: .asciz "n:u"

main:
 sub $8, %rsp
 
 #If you return from main, push r12 and r13 (and then pop them)
 
 #Move the args to non-volatile registers
 mov %rdi, %r12     #R12 = argc
 mov %rsi, %r13     #R13 = argv
 
parseArgs:
 mov %r12, %rdi
 mov %r13, %rsi
 lea shortOpts(%rip), %rdx
 lea nameOpt(%rip), %rcx
 xor %r8, %r8
 call getopt_long
 
 #Found --name/-n?
 cmp $'n', %al
 je foundName
 
 
 #Found --upper/-u?
 cmp $'u', %al
 je foundUpper
 
 #Everything else is an error or end of option args (-1)
 test %eax, %eax
 jns parseError

 #Args are parsed, optind is the index of the first non optional arg
 
 lea (%r13, %r12, 8), %r12  #R12 = one past the last argument       
 mov optind(%rip), %ecx     #RCX = current index
 lea (%r13, %rcx, 8), %r13  #R13 = pointer to pointer to current argument
 
 #Print the greetings
doGreetings:
 #Stop?
 cmp %r12, %r13
 jae end
 
 #Print the current greetings
 mov greetings(%rip), %rdi
 mov (%r13), %rsi
 mov name(%rip), %rdx
 call printf
 
 #Next arg
 add $8, %r13
jmp doGreetings
 
end:
 xor %edi, %edi
 call exit

foundName:
 #Here optarg is a pointer to the argument value
 #Copy the pointer to name
 
 mov optarg(%rip), %rdx
 mov %rdx, name(%rip)
jmp parseArgs

foundUpper:
 #This option has no argument, we just set the greetings to strHelloUpper
 
 lea strHelloUpper(%rip), %rcx
 mov %rcx, greetings(%rip)
jmp parseArgs

parseError:
 #Just return 1
 
 mov $1, %edi
 call exit

你可以用 GCC 编译这个程序,然后运行它:

./greet Alice Bob Eve
./geeet Alice --name Bob
./greet Alice --upper
./greet --name Eve --upper Alice
./greet -u Alice
./greet Alice -un Bob

【讨论】:

  • 您的第二个版本,内联 strcmp,实际上将其内联为memcmp(argv[1], "-i", 2),而不是检查终结者, 只是检查第一个 arg开始-i。就像问题中的代码似乎在尝试一样,因为它使用了.ascii 而不是.asciz,但这可能是个意外;显然我们不应该从细节中推断出太多。他们不是在比较两个指针,而是在比较 argv[1]8 bytes of ASCII + machine code
  • 避免手动转换为 ASCII 的一种方法是使用 GAS 表达式。喜欢cmpw $'-' | ('i' << 8), (%rdi)。你仍然需要正确的字节顺序,而且它的可读性仍然比 NASM cmp word [rdi], "-i" 要低得多,所以它并没有真正改变你不想在 GAS 中手动做很多事情的观点。
  • @PeterCordes 好点,我忘记了终结者!让我修复它。该代码还假设 arg 至少有两个字节长。也修复了开头的句子,它清楚地比较了指针与数据。谢谢你。
猜你喜欢
  • 2015-02-16
  • 2011-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-11
  • 2013-10-12
  • 1970-01-01
相关资源
最近更新 更多