Skip to content

Commit cbb5d0b

Browse files
committed
add posts
1 parent 77ecbc0 commit cbb5d0b

7 files changed

Lines changed: 506 additions & 0 deletions

_posts/Compiler/2025-10-17-Binary-Format-and-Analysis-with-linking-information.md

Lines changed: 330 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
title: Machine code layout
3+
date: 2025-10-17
4+
categories: [Compiler]
5+
tags: [arm, compiler] # TAG names should always be lowercase
6+
published: false
7+
---
8+
9+
> Problem: I am thinking whether it is possible to convert a linked binary file (EXEC) back to a assembly file (PIC friendly)?
10+
> Which means:
11+
> 1. convert jump target addresses back to label, code position without pc.
12+
> 2. convert data fetching address back to label
13+
> ...
14+
15+
# Resources
16+
- [MachineBlockPlacement.cpp in LLVM](https://llvm.org/doxygen/MachineBlockPlacement_8cpp_source.html)
17+
- [Machine code layout optimizations](https://easyperf.net/blog/2019/03/27/Machine-code-layout-optimizatoins?utm_source=chatgpt.com)
18+
Compilers like to operate on a basic block level, because it is guaranteed that every instruction in the basic block will be executed exactly once. Thus for some problems we can treat all instructions in the basic block as one instruction. This greatly reduces the problem of CFG (control flow graph) analysis and transformations.
19+
20+
# Main idea description
21+
- Basic block placement
22+
- Compare two layouts, the second one is more friendly for cache: the hot path is closer.
23+
- Main reason is because we maintain fall through between hot pieces of the code. Not taken branches are fundamentally cheaper that taken. Additionally second case better utilizes L1 I-cache and uop-cache (DSB).
24+
![compare_two_path](/commons/images/compiler/compare_two_path.png)
25+
- [Basic block alignment Issues](https://easyperf.net/blog/2018/01/18/Code_alignment_issues)
26+
- This is purely micro-architectural optimization which is usually applied to loops. Figure below is the best brief explanation of the matter:
27+
- ![code alignment](/commons/images/compiler/code-alignment.png)
28+
- shift the hot code (in yellow) down using NOPs (in blue) so that the whole loop will reside in one cache line. On the picture below cache line start from c0 and ends at ff. This transformation usually improves I-cache and DSB utilization.
29+
- Function splitting, which can be used to separate hot from cold code. This usually improves the memory locality of the hot code.
30+
- The idea of function splitting is to separate hot from cold code. This usually improves the memory locality of the hot code. Example:
31+
```c
32+
void foo(bool cond1, bool cond2) {
33+
// hot path
34+
if (cond1)
35+
// cold code 1
36+
//hot code
37+
if (cond2)
38+
// cold code 2
39+
}
40+
```
41+
- We might want to cut cold part of the function into it’s own new function and put a call to it instead (Outlining). Something like this:
42+
````c
43+
void foo(bool cond1, bool cond2) {
44+
// hot path
45+
if (cond1)
46+
cold1();
47+
//hot code
48+
if (cond2)
49+
cold2();
50+
}
51+
52+
void cold1() __attribute__((noinline)) { // cold code 1 }
53+
void cold2() __attribute__((noinline)) { // cold code 2 }
54+
```
55+
- Because we just left the call instruction inside the hot path it’s likely that next hot instruction will reside in the same cache line. (Shrinkage the cold code usage in the cache)This improves utilization of CPU Front-End data structures like I-cache and DSB-cache.
56+
- Function Grouping
57+
- Usually we want to place hot functions together such that they touch each other in the same cache line.
58+
- This optimization works best when there are many small hot functions.
59+
- There is also very cool tool for doing this automatically: hfsort. It uses linux perf for getting profile information, then it does it’s ordering magic and gives the text file with optimized function order which you can then pass to the linker. Here is the whitepaper if you want to read more about the underlying algorithms.
60+
- Profile guided optimizations (PGO) - Optmize the app just for that single workload
61+
- Using PGO -- It is usually the best option to use PGO if you can come up with a typical scenario for your application.
62+
- Given profiling information compiler doesn’t need to question what should be the decision. And often times you will see in compiler implementation the pattern like:
63+
```c
64+
if (profiling information available)
65+
make decision based on profiling data
66+
else
67+
make decision based on heuristics
68+
```
69+
# What is more?
70+
- some research works reorder functions using PGO (function reordering), or newer techniques like basic-block sections (each block in its own section, so the linker can stitch them using profile).
71+
- Google’s [Propeller](https://research.google/pubs/propeller-a-profile-guided-relinking-optimizer-for-warehouse-scale-applications/?utm_source=chatgpt.com) relinks using basic-block sections to build globally optimal hot traces without disassembling.
72+
- Post-link optimizers like [BOLT](https://research.facebook.com/publications/bolt-a-practical-binary-optimizer-for-data-centers-and-beyond/?utm_source=chatgpt.com) (Meta/Facebook) profile running code and then physically reorder blocks and functions in the final binary to shrink I-cache/I-TLB misses and improve branch prediction.
73+
- Net effect: the dumped binary’s block order is the result of backend MBP decisions + (optionally) profile-guided function/block reordering at link or post-link time. Taken branches might be flipped to make the likely path fall-through, which causes padding/alignment NOPs around hot blocks.
74+
75+
## Answers to my problems
76+
- “Turn PC-relative addresses into labels and group to basic blocks?”
77+
- Yes. Disassemblers compute targets and name them; tools like Ghidra/radare2 build the full CFG and show basic blocks with fall-through and branch edges. Exported assembly will have labels for block entries. (For reassembleable output, use DDisasm/GTIRB.)
78+
- “How is fall-through vs branch chosen?”
79+
- The backend’s block placement and branch selection (plus PGO) try to make the hot successor the fall-through and the cold edge the explicit branch; then layout & alignment reduce taken-branch penalties.
80+
- “How is the final physical order decided?”
81+
- Backend MBP first; then (optionally) Propeller/BOLT or linker’s PGO can reorder functions/blocks using basic-block sections or post-link rewriting, guided by real profiles.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
title: Tools Related to Lifting
3+
date: 2025-10-19
4+
categories: [Compiler]
5+
tags: [arm, compiler] # TAG names should always be lowercase
6+
published: false
7+
---
8+
9+
> Problems: I want to find out tools about lifting binaries to assembly code, while preserving relocation and linking information.
10+
> Problems continued from the last post, I want to convert an executable binary back to position-independent assembly code.
11+
12+
## Existing tools
13+
- [Binary Ninja](https://cloud.binary.ninja/bn/cac518d6-6e84-4ae9-a338-a9ec0aacd074?view=Triage&func=66800&il=3&address=66800): which can be used online
14+
- which has a cfg view, we can get the basic blocks placement information from it.
15+
- In Binary Ninja, I noticed there actually has already a mapping from pc related address to the data itself.
16+
- ![binary ninja data mapping](/commons/images/compiler/binary_ninja.png)
17+
- Also, I noticed that the cfg (needed to place the basic blocks) is also generated by it.
18+
- ![binary ninja control flow graph](/commons/images/compiler/binary_ninja_cfg.png)
19+
- [DDisasm](https://github.com/GrammaTech/ddisasm): a fast disassembler which is accurate enough for the resulting assembly code to be reassembled.
20+
| Stack | Core idea | Typical output | Sweet spot |
21+
| --- | --- | --- | --- |
22+
| **DDisasm → GTIRB → gtirb-pprinter** | Disassemble to a **structured binary IR** (GTIRB) that preserves sections, blocks, edges, symbols, and then pretty-print **re-assemblable .s** | `.gtirb` (graph+metadata) → **assembly with labels per basic block** | When you want a **faithful, labeled assembly** you can tweak and (often) reassemble/relink; also nice for **programmatic block reordering** and regeneration |
23+
| **angr + Ramblr** | **Lift** binary to VEX IR with powerful static/dynamic analyses (CFG, dataflow, symbolic exec); **Ramblr** reconstructs a **reassemblable** view for **patching/rebuilding** | Python objects (CFG, IR), patched **binary**, or assembly-like output for rewriting | When you need **analysis first** (CFG, slicing, constraint solving) and then **surgical patching** or a **reassembled** artifact to preserve semantics |
24+
25+
Functional differences (practical)
26+
==================================
27+
28+
* **IR and ecosystem**
29+
30+
* **GTIRB** is a **file/graph IR** for binaries (sections, byte intervals, code blocks, CFG, symbols). Great for **structural transforms** (e.g., reorder blocks, move data). It’s “printer-friendly.”
31+
32+
* **angr** is an **analysis framework** (lifting to **VEX**, symbolic execution, dataflow). It excels at **discovering** things (CFG recovery quality, constant/alias resolution, indirect jumps) and **reasoning** about feasibility.
33+
34+
* **Re-assemblable output**
35+
36+
* **GTIRB + pprinter**: generates **clean GAS** with **one label per basic block**, jump tables in `.rodata`, etc. Very convenient when your goal is **labeled .s** that you can feed to `as/ld`.
37+
38+
* **Ramblr**: focuses on making a **reassembled/patchable** binary; it reconstructs relocations/symbols enough to produce an executable that preserves behavior. Output assembly is more “for rewriting” than for human editing, but it can be used that way.
39+
40+
# Usage of DDisasm
41+
42+
- Using docker
43+
```shell
44+
# Directly use the built docker
45+
docker pull grammatech/ddisasm:latest
46+
sudo docker images
47+
REPOSITORY TAG IMAGE ID CREATED SIZE
48+
grammatech/ddisasm latest 0c4892488a8d 7 weeks ago 429MB
49+
# run command and delete
50+
sudo docker run --rm grammatech/ddisasm:latest ls /usr/local/bin
51+
ddisasm
52+
gtirb-layout
53+
gtirb-pprinter
54+
# check latest version
55+
sudo docker run --rm grammatech/ddisasm:latest ddisasm --version
56+
1.9.2 (1cd04b14 2025-08-26) ARM64+IA32+X64+ARM32+MIPS32
57+
```
58+
- disassembling the binary, generate the gtirb and asm file
59+
```shell
60+
sudo docker run --rm -v $PWD:/workspace -w /workspace grammatech/ddisasm:latest ddisasm Shootout-nestedloop --ir Shootout-nestedlo
61+
op.gtirb
62+
Building the initial gtirb representation [ 4ms]
63+
Processing module: Shootout-nestedloop
64+
disassembly load [ 5ms] compute [ 23ms] transform WARNING: Could not find GLOBAL/WEAK symbol for _DYNAMIC
65+
[ 2ms]
66+
SCC analysis compute [ 0ms] transform [ 0ms]
67+
no return analysis load [ 0ms] compute [ 0ms] transform [ 0ms]
68+
function inference load [ 0ms] compute [ 0ms] transform [ 0ms]
69+
sudo docker run --rm -v $PWD:/workspace -w /workspace grammatech/ddisasm:latest ddisasm Shootout-nestedloop --asm Shootout-nestedl
70+
oop-ddisasm.s
71+
Building the initial gtirb representation [ 4ms]
72+
Processing module: Shootout-nestedloop
73+
disassembly load [ 5ms] compute [ 23ms] transform WARNING: Could not find GLOBAL/WEAK symbol for _DYNAMIC
74+
[ 2ms]
75+
SCC analysis compute [ 0ms] transform [ 0ms]
76+
no return analysis load [ 0ms] compute [ 0ms] transform [ 0ms]
77+
function inference load [ 0ms] compute [ 0ms] transform [ 0ms]
78+
Printing assembler [ 6ms]
79+
```
80+
- use gtirb-pprinter to view the generated gtirb file
81+
```shell
82+
sudo docker run --rm -v $PWD:/workspace -w /workspace grammatech/ddisasm:latest gtirb-pprinter --ir Shootout-nestedloop.gtirb --as
83+
m gtirbpprinter-shootout-nestedloop.s
84+
[sudo] password for qingchen:
85+
[INFO] (/usr/local/src/gtirb-pprinter/src/gtirb_pprinter/driver/pretty_printer.cpp:291) Reading GTIRB file: "Shootout-nestedloop.gtirb"
86+
[INFO] (/usr/local/src/gtirb-pprinter/src/gtirb_pprinter/driver/pretty_printer.cpp:577) Generating assembly file for module Shootout-nestedloop
87+
```
88+
Seems the generated assembly file has minimal differences with the assembly file directly disassembled from the binary
89+
```shell
90+
diff gtirbpprinter-shootout-nestedloop.s Shootout-nestedloop-ddisasm.s
91+
524a525
92+
> .L_21030:
93+
545d545
94+
< .L_21030:
95+
```
176 KB
Loading
79.7 KB
Loading
158 KB
Loading
92.1 KB
Loading

0 commit comments

Comments
 (0)