To understand what's happening, you need to learn about Position Independent Code (PIC).
In a nutshell, the compiler wants the executable code to be correct independent of where in memory it gets loaded. In the case of a shared library, the OS may load it at a different place every time; even if statically linked, PIC will make the linker's life easier.
The "trick" that's normally used on ARM processors to produce PIC is using PC (Program Counter) relative addresses. The compiler doesn't produce code that says "the string is at address 0x123456", it produces "the string is 0x1234 bytes behind this instruction". Thus, when the program is moved in memory, the "0x1234" stays the same.
Which is why your 2nd instruction adds the program counter to the relative address in R0.
Now, to know what ADD R0, PC
really does, you need to know how the processor works. The processor is in Thumb (2 byte instructions) mode as you can see from the 2 bytes difference between 9F52
and 9F54
, and when the processor executes the ADD R0, PC
instruction, the prefetch unit will already have read the first two bytes of BLX _MSlog
. So the PC that gets added is actually 9F56
. This should answer your first question: in the display of the opcode's meaning, IDA subtracts the value it knows to be added in the next instruction.
Second question: I don't know the internals of IDA, but I'm 99% sure it looks for ADD Rx, PC
instructions, and produces a string reference like yours at the preceding instruction that loads Rx
, just because it knows this is the standard way of achieving PIC on ARM.
Third question: Manually disasembling Thumb is hard - really hard - as you can see if you check the Thumb instruction set. And no, 4a f2 a6 50
isn't just a data offset; it loads the lower 16 bits of the value, and C0 F2 01 00
loads the higher 16 bits of the value. The Online Disassembler translates those two sets of 4 byte instructions to
movw r0, #42406 ; 0xa5a6
movt r0, #1
and you can read here about the movw
and movt
instructions. IDA is friendly enough to show all of this as one single 8-byte instruction, but under the hood, a lot more is going on.
So if you really want to change an offset like this, get familiar with the thumb manual I linked above, learn each and every bit, and assemble the new instructions manually. Or, just (ab)use the gnu assembler in the way I outlined in my answer to this question. Just write a bare minimum assembler snippet and have as
handle the gory details for you.
But, as @joxeankoret said: if you just want to change a string, and don't need the original string anwhere else, and the new string isn't longer than the old one, overwriting the string with the new one will be much easier than finding a place for your new string and adjusting the offsets. You can jump to that position by double clicking the label in IDA, or just scroll to the address 2FF4C
(1A5A6+9F56
).