Apollo 13 - Tao Liu's blog
Failure is not an option.
Saturday, February 14, 2026
Dynamic ELF Structure - Endless Segment Fault
Wednesday, February 4, 2026
RiscV Assembly Got/PLT index Calculation
I searched many online document about RiscV PLT table and how the relocation works. Eventually I got my own version working. This is my notes in case one day I will forget.
Based on
the PLT/GOT RISC V code, the lazy binding code is like the following two
tables. The first one is the PLT[0] code stub where the _dl_runtime_resolver
code is being invoked. The t3 stores the _dl_runtime_resolver address and the
jr t3 is to call that function. That function will requires two parameters: t0
holds the link map address, and t1 holds the function (e.g. printf) offset in
the .got.plt. In the code, t2 is used as temporary register.
The summary
is below
· t3 is the _dl_runtime_resolver function address stored in GOT
· t0 is the link map address in GOT
· t1 is the function (e.g. printf) offset in .got.plt
· t2 is temporary register
The
hdr_size is the PLT[0] size which is two 16 bytes (32 bytes) size section. It
has 8 instructions. In the following code, PTRSIZE = 4 if 32bits and PTRSIZE =
8 if 64bits
Table
1 PLT[0] Code Stub
|
1:
auipc t2, %pcrel_hi(.got.plt)
sub t1, t1, t3 # shifted .got.plt offset +
hdr size + 12
l[w|d] t3, %pcrel_lo(1b)(t2)
# _dl_runtime_resolve
addi t1, t1, -(hdr size + 12)
# shifted .got.plt offset, hdr_size is PLT0 size (32 bytes)
addi t0, t2,
%pcrel_lo(1b) # &.got.plt
srli t1, t1, log2(16/PTRSIZE)
# .got.plt offset
l[w|d] t0, PTRSIZE(t0)
# link map
jr t3 |
And the
above code is invoked by the following code. The following code is a function
(e.g. printf) stub in plt section.
Table
2 PLT[N] Code Stub
|
1:
auipc t3,
%pcrel_hi(function@.got.plt)
l[w|d] t3, %pcrel_lo(1b)(t3)
jalr t1, t3
nop |
How to get offset in t1
register
The t1 is
used to compute the got.plt offset from the plt code stub.
When the
code called from Table
2 PLT[N] Code Stub to Table
1 PLT[0] Code Stub. The first time the Table 2 PLT[N] Code Stub is called, the function@.got.plt is points to PLT[0]. The PLT[0]’s
job is to update the function@.got.plt to actually function address.
Table 2 PLT[N] Code Stub set t1 and t3 to the following
value
· t1 = &nop = &PLT[N] + 12,
o
12
is from 3 instruction and each instruction is 4 bytes
· t3 = &PLT[0]
In the Table 1 PLT[0] Code Stub, the t1 was computed
t1 = [t1 –
t3 – (hdr_size + 12)] >>
= [&PLT[N] + 12 - &PLT[0] –
hdr_size – 12] >>
= [&PLT[N] - &PLT[0] – hdr_size]
>> (where
hdr_size is the PLT[0] size, it is 32bytes, refer to the diagram below)
= [(N-1)*16] >>
= =
= (N-1)PTRSIZE
N starts from 1 because PLT[0] is reserved for
dynamic resolver. So the 1st function is at 0 and 2nd
function is at PTRSIZE. The PTRSIZE is the address data size.
· If 32bit address, the PTRSIZE is 4,
which is 4 bytes equals 32 bits.
· If 64bit address, the PTRSIZE is 8,
which is 8 bytes equals 64 bits.
Please note
the GOT.PLT has 1st and 2nd element to be reserved value.
The 1st element is PLT[0] address and the 2nd element is
link map address.
Link map into t0
The link
map is a reserve entry in got.plt section. The link_map is a data structure
used internally by the Linux dynamic linker (ld.so) to keep track of all shared
libraries (shared objects, .so files) loaded into a running process.
Executable (.got.plt)
│
├── [0] => address of resolver entry
(__dl_runtime_resolve)
├── [1] => pointer to link_map (used
by resolver)
├── [2] => address of function
(e.g., printf)
Please note this is not the end. If you want to call printf, you will need to implement the gnu version table and hash algorithm. I tried both old hash algorithm and new hash algorithm, both works. My environemnt is QEMU and StarFive2 Ubuntu Linux.
Tuesday, January 13, 2026
Rust Macro
Thursday, January 1, 2026
Tao's Fragments: RiscV Linux lib ELF Generation
.data.extern printftt_fmt: .string "%d\n"const_float_or_string_value_104:.string "Array index access test, expected value 42 and actual value: %d\n".text.globl add2.type add2, @functionadd2:add a0, a0, a1ret.size add2, .-add2exit 42
The .extern will trigger dynamic symbol structure generation. The key here is the add2 function related instruction which is in bold font.
- globl add2 is to add "add2" to the dynamic symbol (.dynsym) table.
- add2 shows the starting point of add2 function and it will be used to compute the size of the add2 function.
- .size show the add2's size is equal to current (the small dot) minus add2.
#include <stdio.h>int add2(int a, int b);int main() {printf("%d\n", add2(20, 22));return 0;}
Friday, December 19, 2025
Compiler AST & Mermaid
Sunday, October 18, 2020
SelfNote: Enable Python in Visual Studio Interactive
Reference a local library
import osos.chdir(r"c:\mycode\xyz\")
Make sure the string is started with "r".
Reload a library
import myModule #import my module
#the following is to reload myModule
import importlib
importlib.reload(myModule )
Please note: myModule does not need quote or double quote
Friday, August 7, 2020
Matrix Multiplication & Graph Reachable
K = M ⊗ M
Each element b in N can be calculated by the following formula, where N is the number of the nodes (or the column number of the matrix M).
- there is a direct edge between i and j
- there is a node p which is reachable from i and p can reach j
- M(i, j) = 1
- 彐p, M(i, p) = 1 and M(p, j) = 1


