Addressing mode

133 阅读2分钟

There are actually three way to find the location of data your code access, as following picture shows.

1. Absolute addressing

When your code know the fixed address of data in compile time or link time, you can access the data directly. Because later on in runtime, the code segment is readonly, if the address of data is hard wire in code, there is no way to change it. For example, as we all know, in a machine, its memory can be seen as a large array of bytes, each byte has its own address from 0 to the largest address, e.g. 4G - 1. Then if the data is locate at 0x8000, for instance, we access it directly from that absolute address, such as load r0, 0x8000, load the content at 0x8000 to register r0.

2. PC-relative addressing and its variant register-indirect addressing

Absolute addressing has its advantage as directness, no overhead to calculate the address of content. By the way, we want to access some data and the only way to approach it is to know its address, therefore Addressing mode specifies the way we know the address of needed data.

But what if the address of data is not fixed when the code is compiled, then we need to figure out what information we can take advantage of.

For global data, such as data in .data, .rodata section, for fully linked executable, the code know the offset or distance between data and current code. Then we can use pc-relative addressing to access that data, e.g. load r0, 0x80(pc).

For local data, because it is created at stack in entry of function, therefore the code absolutely cannot know its address in compile time or link time, only in runtime instead. So we can access those data by register-based address with stack pointer register or frame pointer register, e.g. load r0, 2(sp) or load r0, -1(fp).

3. GOT/PLT addressing and its variant memory-indirect addressing

What if there are two shared libraries A and B loaded in to a process, and code in shared A want to access data from shared B. Then we need to somewhere to place the address of data from shared B for shared A. In shared A, its code can access the its data by pc-relative addressing. So there is a GOT (Global Object Table) for recording external symbol and later patch up address. code in shared library can use GOT to access external symbol. And the dynamic linker will find the address of requiring symbol and then patch up into that GOT records. For external function is little different but core is the same, it uses PLT(Procedure Linking Table) to record the address with external function symbol. A call is actually is a jump to subroutin with preparations. There for in code of shared library access external function, it will jump to a specific entry of GOT, e.g. jmp 4 + @GOT(PC). The first access time without patched up, address recorded in 4 + @GOT(PC) is a stub to find the required address and do the patched-up, then jump to required address. The later access time, because of patched-up, the jmp 4 + @GOT(PC) will jump to the required subroutin directly.

4. One more thing, PIC and PIE.

PIC stands for Position independent code, and PIE stands for Position independent excutable. In other words, those code and excutable can be run properly no matter where they are loaded into memory. So using PC-relative addressing and GOT/PLT, it is quite straight to generate PIC and PIE with some cares.