Rick Dearman
Rick Dearman

Reputation: 386

Access ARGV in arm assembly language

I'm doing what should be a fairly simple exercise to print the number of arguments passed to a program, and then print the first argument as a string. I get the number of the arguments, but the printing seems to just give random memory locations. I run the program like this:

argdisplay ab ac ad

which correctly prints the number of arguments, but not the argument string.

Argc: 4
Argc: �

My program is:

.data
stringD:     .asciz "Argc: %d\n"
stringS:     .asciz "Argc: %s\n"    

.text
.global main

main:
no_args:    
    push {ip, lr}
    mov r1, r0
    ldr r0, =stringD
    bl printf
arg_string: 
    ldr r1, [sp, #8]
    ldr r0, =stringS
    bl printf
    pop {ip, pc}

.global printf
.end

My understanding of the argv is that it is an array separated by four bytes, so I put in #8 to try and print the second argument.

Upvotes: 1

Views: 477

Answers (1)

Nate Eldredge
Nate Eldredge

Reputation: 57922

The argv argument isn't passed on the stack. It's the second argument to your main function, so it is in r1 on entry to main. So:

  • You need to save it (or a pointer to the desired string) before overwriting r1 for the call to printf

  • You need to save this in a call-preserved register (r4-r8, r10) (or on the stack, but that is less convenient)

  • You need to save and restore that register at the beginning and end of your function

Here is a fixed version. Since argv from r1 is a pointer to the zeroth pointer in the argv array, then 4 bytes later will be the pointer to the first argument argv[1], the one you want to print. We save this in r4 around the first call to print, and add r4 to the list of registers to be pushed and popped.

main:
    push {r4, ip, lr}
    ldr r4, [r1, #4]

    mov r1, r0
    ldr r0, =stringD
    bl printf

    mov r1, r4
    ldr r0, =stringS
    bl printf

    mov r0, #0      // return 0
    pop {r4, ip, pc}

A couple other notes:

  • We zero out r0 on exit so that our main function returns 0 indicating success. Returning a random value is annoying to users who want to test the exit code.

  • You probably don't want to label both outputs as Argc.

  • .global printf at the end should probably be .extern printf. You aren't defining printf in this module, you are telling the assembler that it will be found in another module. Also it would make more sense to put this directive before the calls to printf (maybe at the top of the file).

  • The labels no_args and arg_string aren't used as branch targets anywhere, so they have no effect and might as well be comments (in which case they could be more descriptive).

  • .end is unnecessary, unless you are going to put additional stuff after it that you want the assembler to ignore. It's more likely to be an annoyance, if you add more actual code at the end of the file and then wonder why the assembler isn't assembling it.

  • Since you don't intend to write to stringD or stringS, put them in the read-only data section. Replace .data with .section .rodata.

  • It would be nice to handle gracefully the case when no arguments are passed. Currently it sort of works, since if no arguments are present then argv[1] is null, and printf on Linux accepts a null pointer with %s and prints the string (null). But you could think about nicer options.

Upvotes: 1

Related Questions