5

In windows platform, an application usually references its IAT(Import Access Table) to get the address of the APIs it wants, then call it. Then some mechanisms are done as demonstrated here nicely.

However, I cannot find out how API calls work on Android NDK. I think there's no IAT in an ELF file. Could anybody tell me how API calls on android works, in assembly level?

E.g.

When I call ALooper_acquire(&mylooper) then it assembles as

 mov r4, 0x2000  ; address of mylooper
 bl 0x7777fff0      ;address of ALooper_acauire
 ... after ALooper_acquire()

then in 0x7777fff0:

Blah blah happens to call system api, ...
5

To answer your question, let us first set a solid ground in terms of entities and definitions.

ELF stands for "Executable and Linkable format".
That is, it defines the structure and shape of two types of files:

  • Executables (Shared Objects *.so and stand-alone executables)
  • Linkables (Object files *.o)

Let us focus on executables.

Dependencies resolution of executables

Among other things, ELF defines a method of describing and resolving dependencies of executable.

Dependencies

Put simple, dependencies are required external symbols. Symbols are named (identified) chunks of memory. Some of the chunks are data chunks (Global variables) while others are code-data chunks (Global functions). Since a symbol is a part of a module (aka Shared Object), any required symbol is coupled with a module.

In summary, dependencies are needed symbols and modules.

Note that a function that is a part of an OS API could be and usually is an external symbol. However, it's not always the case.

Dependencies description

ELF defines a structure called Dynamic Segment used to store information needed by the loader (aka dynamic linker) in the loading process of an executable. An executable's dependencies description is stored in its Dynamic Segment.

Needed symbols are organized in a table called Dynamic Symbol Table that's referred by the Dynamic Segment:

] Reference to a symbol table under Loader directives- https://elfy.io/KYze4

A dynamic symbol table is a contiguous array of symbol descriptors:

enter image description here .dynsym under Symbols - https://elfy.io/KYze4

Needed modules on the other hand are described directly with DT_NEEDED entries:

enter image description here Needed modules under Loader directives - https://elfy.io/KYze4

Dynamic link

Now we are ready to discuss the wiring mechanism that lets an executable reach its dependencies once they are resolved by the loader. We will do it by following the steps of an external function call.

Let's take a call to __android_log_print as an example (ARM 32 bit).

...
   1d21a:       f7fa e8e8       blx     173ec ; __android_log_print@plt
...

The above is an assembly that calls __android_log_print which prints out text to Android Logcat. But in fact, that blx instruction branches to a specific code-stub in a special area called Procedure Link Table (PLT). There's a code stub in the PLT for every needed external function.

Here's __android_log_print's stub:

...
000173ec __android_log_print@plt:
   173ec:       e28fc600        add     ip, pc, #0, 12
   173f0:       e28cca11        add     ip, ip, #69632   
   173f4:       e5bcf9f4        ldr     pc, [ip, #2548]! 
000173f8 sleep@plt:
   173f8:       e28fc600        add     ip, pc, #0, 12
   173fc:       e28cca11        add     ip, ip, #69632
   17400:       e5bcf9ec        ldr     pc, [ip, #2540]!
...

The three instructions in the stub do the following: (pseudo code)

JUMP *(GOT_ADDRESS + GOT_OFFSET_OF(__android_log_print))

The Global Offset Table (GOT) is a table of pointers. There's a cell in the GOT for every external function. That is, every external function has its own cell in the GOT. Once the loading process is done, the cell of function X contains the memory address of function X.

  • The address computation of the right cell in the GOT is split into 3 because of encoding limitations. eg: Large offsets can't be encoded in a single instruction.

It's the OS loader responsibility to initialize the GOT with the right memory addresses, based on the information discussed before.

The PLT and GOT are parts of the ELF specification.

  • Thank you so much!! Sounds like this applies to any linux executables, right? – KYHSGeekCode Dec 12 '18 at 16:15
  • @KYHSGeekCode I have yet to see a Linux executable that's formatted with a format other than ELF. So it's safe to assume that a Linux executable would be an ELF file, and in such case, yes, the mechanism would be the same. – Lero Dec 12 '18 at 17:17
  • ELF was not Linux's initial dynamic executable format. And there are some static binary formats in use today. But what's really notable about Android is that it uses a unique libc. Some embedded systems use alternate libc's as well. – Chris Stratton Dec 24 '18 at 13:25

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.