Skip to content

Commit a82abfd

Browse files
authored
Merge pull request #1583 from technikelly/feature_raw_bin_blob
2 parents d81051c + 04236d7 commit a82abfd

File tree

12 files changed

+328
-9
lines changed

12 files changed

+328
-9
lines changed

examples/blob_raw.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[CODE]
2+
load_address = 0x10000000
3+
entry_point = 0x10000008
4+
ram_size = 0xa00000

examples/hello_arm_blob_raw.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
##############################################################################
2+
# This example is meant to demonstrate the modifications necessary
3+
# to enable code coverage when emulating small code snippets or bare-metal
4+
# code.
5+
##############################################################################
6+
from qiling import Qiling
7+
from qiling.const import QL_ARCH, QL_OS, QL_VERBOSE
8+
from qiling.extensions.coverage import utils as cov_utils
9+
from qiling.loader.loader import Image
10+
import os
11+
12+
BASE_ADDRESS = 0x10000000
13+
CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8
14+
END_ADDRESS = 0x100000ba
15+
DATA_ADDR = 0xa0000000 # Arbitrary address for data
16+
STACK_ADDR = 0xb0000000 # Arbitrary address for stack
17+
18+
# Python implementation of the checksum function being emulated
19+
# This checksum function is intended to have different code paths based on the input
20+
# which is useful for observing code coverage
21+
def checksum_function(input_data_buffer: bytes):
22+
expected_checksum_python = 0
23+
input_data_len = len(input_data_buffer)
24+
if input_data_len >= 1 and input_data_buffer[0] == 0xDE: # MAGIC_VALUE_1
25+
for i in range(min(input_data_len, 4)):
26+
expected_checksum_python += input_data_buffer[i]
27+
expected_checksum_python += 0x10
28+
elif input_data_len >= 2 and input_data_buffer[1] == 0xAD: # MAGIC_VALUE_2
29+
for i in range(input_data_len):
30+
expected_checksum_python ^= input_data_buffer[i]
31+
expected_checksum_python += 0x20
32+
else:
33+
for i in range(input_data_len):
34+
expected_checksum_python += input_data_buffer[i]
35+
expected_checksum_python &= 0xFF # Ensure it's a single byte
36+
return expected_checksum_python
37+
38+
def unmapped_handler(ql: Qiling, type: int, addr: int, size: int, value: int) -> None:
39+
print(f"Unmapped Memory R/W, trying to access {size:d} bytes at {addr:#010x} from {ql.arch.regs.pc:#010x}")
40+
41+
def emulate_checksum_function(input_data_buffer: bytes) -> None:
42+
print(f"\n--- Testing with input: {input_data_buffer.hex()} ---")
43+
44+
test_file = "rootfs/blob/example_raw.bin"
45+
46+
with open(test_file, "rb") as f:
47+
raw_code: bytes = f.read()
48+
49+
ql: Qiling = Qiling(
50+
code=raw_code,
51+
archtype=QL_ARCH.ARM,
52+
ostype=QL_OS.BLOB,
53+
profile="blob_raw.ql",
54+
verbose=QL_VERBOSE.DEBUG,
55+
thumb=True
56+
)
57+
58+
''' monkeypatch - Correcting the loader image name, used for coverage collection
59+
removing all images with name 'blob_code' that were created by the blob loader.
60+
This is necessary because some code coverage visualization tools require the
61+
module name to match that of the input file '''
62+
ql.loader.images = [img for img in ql.loader.images if img.path != 'blob_code']
63+
ql.loader.images.append(Image(ql.loader.load_address, ql.loader.load_address + ql.os.code_ram_size, os.path.basename(test_file)))
64+
65+
input_data_len: int = len(input_data_buffer)
66+
67+
# Map memory for the data and stack
68+
ql.mem.map(STACK_ADDR, 0x2000)
69+
ql.mem.map(DATA_ADDR, ql.mem.align_up(input_data_len + 0x100)) # Map enough space for data
70+
71+
# Write input data
72+
ql.mem.write(DATA_ADDR, input_data_buffer)
73+
74+
# Set up the stack pointer
75+
ql.arch.regs.sp = STACK_ADDR + 0x2000 - 4
76+
# Set up argument registers
77+
ql.arch.regs.r0 = DATA_ADDR
78+
ql.arch.regs.r1 = input_data_len
79+
80+
# Set the program counter to the function's entry point
81+
ql.arch.regs.pc = CHECKSUM_FUNC_ADDR
82+
83+
# Set the return address (LR) to a dummy address.
84+
ql.arch.regs.lr = 0xbebebebe
85+
86+
ql.hook_mem_unmapped(unmapped_handler)
87+
#ql.debugger="gdb:127.0.0.1:9999"
88+
89+
# Start emulation
90+
print(f"Starting emulation at PC: {hex(ql.arch.regs.pc)}")
91+
try:
92+
with cov_utils.collect_coverage(ql, 'drcov', 'output.cov'):
93+
ql.run(begin=CHECKSUM_FUNC_ADDR, end=END_ADDRESS)
94+
except Exception as e:
95+
print(f"Emulation error: {e}")
96+
97+
print(f"Emulated checksum: {hex(ql.arch.regs.r0)}")
98+
99+
if __name__ == "__main__":
100+
data = b"\x01\x02\x03\x04\x05" # Example input data
101+
emulate_checksum_function(data)

examples/rootfs

examples/src/blob/Makefile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Makefile for Bare-Metal ARM Checksum Calculator
2+
3+
# --- Toolchain Definitions ---
4+
TOOLCHAIN_PREFIX = arm-none-eabi
5+
6+
# Compiler, Linker, and Objcopy executables
7+
CC = $(TOOLCHAIN_PREFIX)-gcc
8+
LD = $(TOOLCHAIN_PREFIX)-gcc
9+
OBJCOPY = $(TOOLCHAIN_PREFIX)-objcopy
10+
11+
# --- Source and Output Files ---
12+
SRCS = example_raw.c
13+
OBJS = $(SRCS:.c=.o) # Convert .c to .o
14+
ELF = example_raw.elf
15+
BIN = example_raw.bin
16+
17+
# --- Linker Script ---
18+
LDSCRIPT = linker.ld
19+
20+
# --- Compiler Flags ---
21+
CFLAGS = -c -O0 -mcpu=cortex-a7 -mthumb -ffreestanding -nostdlib
22+
23+
# --- Linker Flags ---
24+
LDFLAGS = -T $(LDSCRIPT) -nostdlib
25+
26+
# --- Objcopy Flags ---
27+
OBJCOPYFLAGS = -O binary
28+
29+
# --- Default Target ---
30+
.PHONY: all clean
31+
32+
all: $(BIN)
33+
34+
# Rule to build the raw binary (.bin) from the ELF file
35+
$(BIN): $(ELF)
36+
$(OBJCOPY) $(OBJCOPYFLAGS) $< $@
37+
@echo "Successfully created $(BIN)"
38+
39+
# Rule to link the object file into an ELF executable
40+
$(ELF): $(OBJS) $(LDSCRIPT)
41+
$(LD) $(LDFLAGS) $(OBJS) -o $@
42+
@echo "Successfully linked $(ELF)"
43+
44+
# Rule to compile the C source file into an object file
45+
%.o: %.c
46+
$(CC) $(CFLAGS) $< -o $@
47+
@echo "Successfully compiled $<"
48+
49+
# --- Clean Rule ---
50+
clean:
51+
rm -f $(OBJS) $(ELF) $(BIN)
52+
@echo "Cleaned build artifacts."

examples/src/blob/example_raw.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// example checksum algorithm to demonstrate raw binary code coverage in qiling
2+
// example_raw.c
3+
4+
// Define some magic values
5+
#define MAGIC_VALUE_1 0xDE
6+
#define MAGIC_VALUE_2 0xAD
7+
8+
// This function calculates a checksum with branches based on input data
9+
// It takes a pointer to data and its length
10+
// Returns the checksum (unsigned char to fit in a byte)
11+
unsigned char calculate_checksum(const unsigned char *data, unsigned int length) {
12+
unsigned char checksum = 0;
13+
14+
// Branch 1: Check for MAGIC_VALUE_1 at the start
15+
if (length >= 1 && data[0] == MAGIC_VALUE_1) {
16+
// If first byte is MAGIC_VALUE_1, do a simple sum of first 4 bytes
17+
// (or up to length if less than 4)
18+
for (unsigned int i = 0; i < length && i < 4; i++) {
19+
checksum += data[i];
20+
}
21+
// Add a fixed offset to make this path distinct
22+
checksum += 0x10;
23+
}
24+
// Branch 2: Check for MAGIC_VALUE_2 at the second byte
25+
else if (length >= 2 && data[1] == MAGIC_VALUE_2) {
26+
// If second byte is MAGIC_VALUE_2, do a XOR sum of all bytes
27+
for (unsigned int i = 0; i < length; i++) {
28+
checksum ^= data[i];
29+
}
30+
// Add a fixed offset to make this path distinct
31+
checksum += 0x20;
32+
}
33+
// Default Branch: Standard byte sum checksum
34+
else {
35+
for (unsigned int i = 0; i < length; i++) {
36+
checksum += data[i];
37+
}
38+
}
39+
40+
return checksum;
41+
}
42+
43+
// Minimal entry point for bare-metal.
44+
// This function will not be called directly during Qiling emulation,
45+
// but it's needed for the linker to have an entry point.
46+
__attribute__((section(".text.startup")))
47+
void _start() {
48+
// In a real bare-metal application, this would initialize hardware,
49+
// set up stacks, etc. For this example, it's just a placeholder.
50+
// We'll call calculate_checksum directly from our Qiling script.
51+
52+
while (1) {
53+
// Do nothing, or perhaps put the CPU to sleep
54+
asm volatile ("wfi"); // Wait For Interrupt (ARM instruction)
55+
}
56+
}

examples/src/blob/linker.ld

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* linker.ld */
2+
3+
ENTRY(_start) /* Define the entry point of our program */
4+
5+
/* Define memory regions - simple RAM region for this example */
6+
MEMORY
7+
{
8+
ram (rwx) : ORIGIN = 0x10000000, LENGTH = 64K /* 64KB of RAM for our program */
9+
}
10+
11+
SECTIONS
12+
{
13+
/* Define the start of our program in memory.
14+
*/
15+
. = 0x10000000;
16+
17+
.text : {
18+
KEEP(*(.text.startup)) /* Keep the _start function */
19+
*(.text) /* All other code */
20+
*(.text.*)
21+
*(.rodata) /* Read-only data */
22+
*(.rodata.*)
23+
. = ALIGN(4);
24+
} > ram /* Place .text section in the 'ram' region */
25+
26+
.data : {
27+
. = ALIGN(4);
28+
*(.data) /* Initialized data */
29+
*(.data.*)
30+
. = ALIGN(4);
31+
} > ram
32+
33+
.bss : {
34+
. = ALIGN(4);
35+
*(.bss)
36+
*(.bss.*)
37+
. = ALIGN(4);
38+
} > ram
39+
}

examples/uboot_bin.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
ram_size = 0xa00000
33
load_address = 0x80800000
44
entry_point = 0x80800000
5+
heap_address = 0xa0000000
56
heap_size = 0x300000
67

78

qiling/loader/blob.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from qiling import Qiling
77
from qiling.loader.loader import QlLoader, Image
8-
from qiling.os.memory import QlMemoryHeap
98

109

1110
class QlLoaderBLOB(QlLoader):
@@ -28,11 +27,5 @@ def run(self):
2827
# allow image-related functionalities
2928
self.images.append(Image(code_begins, code_ends, 'blob_code'))
3029

31-
# FIXME: heap starts above end of ram??
32-
# FIXME: heap should be allocated by OS, not loader
33-
heap_base = code_ends
34-
heap_size = int(self.ql.os.profile.get("CODE", "heap_size"), 16)
35-
self.ql.os.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size)
36-
3730
# FIXME: stack pointer should be a configurable profile setting
3831
self.ql.arch.regs.arch_sp = code_ends - 0x1000

qiling/os/blob/blob.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from qiling.const import QL_ARCH, QL_OS
99
from qiling.os.fcall import QlFunctionCall
1010
from qiling.os.os import QlOs
11+
from qiling.os.memory import QlMemoryHeap
1112

1213

1314
class QlOsBlob(QlOs):
@@ -49,5 +50,11 @@ def run(self):
4950
# if exit point was set explicitly, override the default one
5051
if self.ql.exit_point is not None:
5152
self.exit_point = self.ql.exit_point
52-
53+
54+
# if heap info is provided in profile, create heap
55+
heap_base = self.profile.getint('CODE', 'heap_address', fallback=None)
56+
heap_size = self.profile.getint('CODE', 'heap_size', fallback=None)
57+
if heap_base is not None and heap_size is not None:
58+
self.heap = QlMemoryHeap(self.ql, heap_base, heap_base + heap_size)
59+
5360
self.ql.emu_start(self.entry_point, self.exit_point, self.ql.timeout, self.ql.count)

tests/profiles/blob_raw.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[CODE]
2+
load_address = 0x10000000
3+
entry_point = 0x10000008
4+
ram_size = 0xa00000

0 commit comments

Comments
 (0)