I tried to request a few clarifications in the comment but you didn't answer, so I'm assuming you are working in an environment that conforms to SYS V x86-64 ABI.
When main
is called, argc
is in rdi
but it is soon overwritten by the calls to strncpy
and printf
itself:
main:
sub rsp, 40
mov rsi, QWORD PTR [rsi+8]
mov edx, 32
mov rdi, rsp ;OOOPS
call strncpy
mov rdi, rsp ;OOOPS
xor eax, eax
call printf
xor eax, eax
add rsp, 40
ret
The code above is the compiled output of your sample program (once cleaned).
But, glibc
on the SYS V x86-64 ABI doesn't synthesize argc
itself (like the Windows' counterpart has to do, see GetCommandLine and similar), this value is passed as the first value on the stack when a program is created (see figure 3.9 of the ABI specifications).

So you can reach it with printf
by using a %d
format that skips the first k - 1 arguments, that is with %k$d
where k is the number to be found.
To find k
you just have to find the offset between rsp
when printf
is called and the address of argc
.
But since argc
is at the bottom of the stack when the process is created, this equals to finding the offset between rsp
at the call site for printf
and the initial value of rsp
.
So using gdb:
gdb --args format-string test
b _start
r
i r rsp
0x7fffffffdfa0 The initial value of RSP
b printf
c
i r rsp
0x7fffffffd9d8 The value AFTER printf is called. Add 8 to find it BEFORE the call
q
Now 0x7fffffffdfa0 - (0x7fffffffd9d8 + 8) = 0x110
0x110 bytes are 34 arguments (0x110/8 = 0x22) and since the first four arguments are in the registers, we need to skip them too, adding 4.
Finally, the count is one based and the difference inclusive so we need to add 2 to the count.
The final value is, for my example environment, 34 + 4 + 2 = 40, leading to the command:
./format-string '%40$d'