Stack Exchange
Decoding ARM instruction from GDB
I want to understand/decode the ARM instructions on my aarch64 device.
I have the following code written in C language:
void test_function(int a, int b, int c, int d) {
  int flag;
  char buffer[10];

  flag = 31337;
  buffer[0] = 'A';
}

int main() {
  test_function(1, 2, 3, 4);
}

gcc -g stack_example.c and gdb -q ./a.out yields the following assembly:
(gdb) disass main
Dump of assembler code for function main:
   0x00000000000016d4 <+0>:     stp     x29, x30, [sp, #-16]!
   0x00000000000016d8 <+4>:     mov     x29, sp
   0x00000000000016dc <+8>:     mov     w0, #0x1                        // #1
   0x00000000000016e0 <+12>:    mov     w1, #0x2                        // #2
   0x00000000000016e4 <+16>:    mov     w2, #0x3                        // #3
   0x00000000000016e8 <+20>:    mov     w3, #0x4                        // #4
   0x00000000000016ec <+24>:    bl      0x16a8 <test_function>
   0x00000000000016f0 <+28>:    mov     w0, wzr
   0x00000000000016f4 <+32>:    ldp     x29, x30, [sp], #16
   0x00000000000016f8 <+36>:    ret
End of assembler dump.
(gdb) disass test_function
Dump of assembler code for function test_function:
   0x00000000000016a8 <+0>:     sub     sp, sp, #0x20
   0x00000000000016ac <+4>:     str     w0, [sp, #28]
   0x00000000000016b0 <+8>:     str     w1, [sp, #24]
   0x00000000000016b4 <+12>:    str     w2, [sp, #20]
   0x00000000000016b8 <+16>:    str     w3, [sp, #16]
   0x00000000000016bc <+20>:    mov     w8, #0x7a69                     // #31337
   0x00000000000016c0 <+24>:    str     w8, [sp, #12]
   0x00000000000016c4 <+28>:    mov     w8, #0x41                       // #65
   0x00000000000016c8 <+32>:    strb    w8, [sp, #2]
   0x00000000000016cc <+36>:    add     sp, sp, #0x20
   0x00000000000016d0 <+40>:    ret
End of assembler dump.

When I now do break 10, break test_function, run and disass main i get
(gdb) disass main
Dump of assembler code for function main:
   0x00000055907a86d4 <+0>:     stp     x29, x30, [sp, #-16]!
   0x00000055907a86d8 <+4>:     mov     x29, sp
   0x00000055907a86dc <+8>:     mov     w0, #0x1                        // #1
   0x00000055907a86e0 <+12>:    mov     w1, #0x2                        // #2
   0x00000055907a86e4 <+16>:    mov     w2, #0x3                        // #3
   0x00000055907a86e8 <+20>:    mov     w3, #0x4                        // #4
=> 0x00000055907a86ec <+24>:    bl      0x55907a86a8 <test_function>
   0x00000055907a86f0 <+28>:    mov     w0, wzr
   0x00000055907a86f4 <+32>:    ldp     x29, x30, [sp], #16
   0x00000055907a86f8 <+36>:    ret
End of assembler dump.

Now according to Arm Architecture Reference Manual Armv8, for A-profile architecture, page 934 the BL instruction starts with 100101 followed by a 26bit immediate value.
Examining the memory at the position of the program counter with yields
(gdb) x/16b 0x55907a86ec
0x55907a86ec <main+24>: 11101111        11111111        11111111        10010111        11100000        00000011        00011111        00101010
0x55907a86f4 <main+32>: 11111101        01111011        11000001        10101000        11000000        00000011        01011111        11010110

I think, the instruction starts in the fourth byte, but i am not sure. I tried to reconstruct the adress 0x55907a86a8, but wasn't able. Could anyone please help?

The address is given relative to the current address.  The instruction is encoded in little endian.  Apply these two hints and you should be able to find the solution.

As fuz said, the offset is little endian so it's 11 11111111 11111111 11101111 which is -17 decimal and is shifted by 2. So, you have 55907a86ec - (17 << 2) = 0x55907a86a8

AArch64 instructions are encoded little-endian, so if you dump the code one byte at a time, each 4-byte word will have the bytes in reverse order.  Therefore, from the output you have, you'll have to take the first 4 bytes, reverse their order (but do not reverse the bits within the bytes), and concatenate them.  (You can get the debugger to do it for you by doing x/tw 0x55907a86ec instead.)  This gives:
10010111111111111111111111101111

Indeed the highest 6 bits are the opcode 100101.  The immediate is 11111111111111111111101111.  This is a negative number in two's-complement (recall that the immediate is sign-extended), with value -17 or -0x11.  This number is multiplied by 4 (shift left two bits), yielding -0x44 and added to the address of the bl instruction itself to find the branch target.  And indeed 0x00000055907a86ec - 0x44 = 0x55907a86a8, which is the address the debugger showed you, and would be the address of the first instruction of test_function.
Note that ASLR was done when you began running the program, which is why the disassembly shows different addresses before and after run.  If you did disassemble test_function after run, you should see it start at 0x55907a86a8.  Nonetheless, if you look at the pre-ASLR disassembly, you'll notice that the relative displacement between the address of the bl test_function instruction (0x16ec) and the address of test_function itself (0x16a8) is the same -0x44.  (Indeed ASLR is done in page units, so the low 12 bits are unchanged by it.)

Thanks! I could never find out about the left-shift by myself. I could only find that the offset of the two adresses 0x...EC - 0x...A8 = 0x44 = 68 and -68=1111 1111 1111 1111 1111 1111 1011 1100 which is indeed the immediate left-shifted two times..  Why is it not coded as -68, but -17? This seems like magic to me and i don't understand why it is this way and one has to first left-shift twice but it seems to works. No hint for this in the reference.

@jahro_me: The Architecture Reference Manual does say (indirectly) that the shift occurs: the decode shows imm26:'00' which is to say "tack on two zero bits", equivalent to a left shift.  And under Assembler Symbols it says "Its offset from the address of this instruction,
in the range +/-128MB, is encoded as imm26 times 4."  The reason is simply that it gains two useful bits.  AArch64 instructions are always aligned to 4 bytes, so if the displacement were encoded in the instruction without a shift, the low 2 bits would always be zero and effectively wasted. [...]

@jahro_me: And in that case, the biggest displacement you could get would be 2^26 bytes = +/- 32 MB.  With the shift, those bits aren't wasted and you can get displacement up to +/- 128 MB, much better for a big program.  In essence, the two low bits can be taken as implicitly zero instead of being actually encoded in the instruction.

Excellent. Yes, i have read about the bits(64) offset = SignExtend(imm26:'00', 64); Makes total sense. I should read more carefully. Best regards and thanks for your excellent answer!
