Skip to content

Commit 8be7c0f

Browse files
authored
tools: Add printer tool (#622)
* commit files * fix formatting * add ci * add testing * devlog and final touches * add tests folder in build.zig.zon * try to fix testing * fix testing and add elfs * refactoring and fix testing * refactor * cleanup and fix formatting
1 parent ed8a7b9 commit 8be7c0f

File tree

19 files changed

+1675
-4
lines changed

19 files changed

+1675
-4
lines changed

.github/workflows/ci.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,27 @@ jobs:
7777
run: zig build test
7878
working-directory: tools/esp-image
7979

80+
test-printer:
81+
name: Test Printer
82+
continue-on-error: true
83+
strategy:
84+
matrix:
85+
os: [ubuntu-latest, windows-latest, macos-latest]
86+
runs-on: ${{ matrix.os }}
87+
steps:
88+
- name: Checkout
89+
uses: actions/checkout@v4
90+
- name: Setup Zig
91+
uses: mlugg/setup-zig@v2
92+
with:
93+
version: ${{ env.ZIG_VERSION }}
94+
- name: Build Printer
95+
run: zig build
96+
working-directory: tools/printer
97+
- name: Test Printer
98+
run: zig build test
99+
working-directory: tools/printer
100+
80101
stm32-gen-check:
81102
name: Check that stm32 generated code is up to date
82103
continue-on-error: true

build.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,15 @@ pub fn MicroBuild(port_select: PortSelect) type {
346346
/// exe.link_function_sections = true;
347347
strip_unused_symbols: bool = true,
348348

349-
/// Unwind tables option for the firmware executable
349+
/// Unwind tables option for the firmware executable.
350350
unwind_tables: ?std.builtin.UnwindTables = null,
351351

352+
/// Error tracing option for the firmware executable.
353+
error_tracing: ?bool = null,
354+
355+
/// Dwarf format option for the firmware executable.
356+
dwarf_format: ?std.dwarf.Format = null,
357+
352358
/// Additional patches the user may apply to the generated register
353359
/// code. This does not override the chip's existing patches.
354360
patches: []const regz.patch.Patch = &.{},
@@ -535,6 +541,8 @@ pub fn MicroBuild(port_select: PortSelect) type {
535541
.single_threaded = options.single_threaded orelse target.single_threaded,
536542
.strip = options.strip,
537543
.unwind_tables = options.unwind_tables,
544+
.error_tracing = options.error_tracing,
545+
.dwarf_format = options.dwarf_format,
538546
}),
539547
.linkage = .static,
540548
}),

tools/printer/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Printer
2+
3+
Printer is a tool to process logging output from your code and print it in a
4+
readable and pretty format. As of now it only annotates addresses from stack
5+
traces with source code locations, but it can be extended later to also print
6+
defmt (when this kind of logger will be added to microzig).
7+
8+
## Use it like a library!
9+
10+
You can easily write a small zig script that flashes the device and then prints
11+
pretty logs to console on `zig build run`. Checkout
12+
[](examples/rp2xxx_runner.zig)

tools/printer/build.zig

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) void {
4+
const target = b.standardTargetOptions(.{});
5+
const optimize = b.standardOptimizeOption(.{});
6+
7+
const serial_dep = b.dependency("serial", .{
8+
.target = target,
9+
.optimize = optimize,
10+
});
11+
12+
const printer_mod = b.addModule("printer", .{
13+
.root_source_file = b.path("src/root.zig"),
14+
.target = target,
15+
.optimize = optimize,
16+
});
17+
18+
const printer_exe = b.addExecutable(.{
19+
.name = "printer",
20+
.root_module = b.createModule(.{
21+
.root_source_file = b.path("src/main.zig"),
22+
.imports = &.{
23+
.{ .name = "printer", .module = printer_mod },
24+
},
25+
.target = target,
26+
.optimize = optimize,
27+
}),
28+
});
29+
b.installArtifact(printer_exe);
30+
31+
const example_exe = b.addExecutable(.{
32+
.name = "rp2xxx_runner",
33+
.root_module = b.createModule(.{
34+
.root_source_file = b.path("examples/rp2xxx_runner.zig"),
35+
.imports = &.{
36+
.{ .name = "printer", .module = printer_mod },
37+
.{ .name = "serial", .module = serial_dep.module("serial") },
38+
},
39+
.target = target,
40+
.optimize = optimize,
41+
}),
42+
});
43+
b.installArtifact(example_exe);
44+
45+
const test_common_mod = b.createModule(.{
46+
.root_source_file = b.path("tests/common.zig"),
47+
.imports = &.{
48+
.{ .name = "printer", .module = printer_mod },
49+
},
50+
});
51+
52+
const generate_test_data_exe = b.addExecutable(.{
53+
.name = "generate_results",
54+
.root_module = b.createModule(.{
55+
.root_source_file = b.path("tests/generate_test_data.zig"),
56+
.imports = &.{
57+
.{ .name = "common", .module = test_common_mod },
58+
.{ .name = "printer", .module = printer_mod },
59+
},
60+
.target = target,
61+
.optimize = optimize,
62+
}),
63+
});
64+
65+
const test_exe = b.addExecutable(.{
66+
.name = "test",
67+
.root_module = b.createModule(.{
68+
.root_source_file = b.path("tests/test.zig"),
69+
.imports = &.{
70+
.{ .name = "common", .module = test_common_mod },
71+
.{ .name = "printer", .module = printer_mod },
72+
},
73+
.target = target,
74+
.optimize = optimize,
75+
}),
76+
});
77+
78+
const generate_test_data_run = b.addRunArtifact(generate_test_data_exe);
79+
if (b.option(bool, "rebuild-test-elf", "rebuild the test program elf") == true) {
80+
const mz_dep = b.lazyDependency("microzig", .{}) orelse return;
81+
82+
const mb = @import("microzig").MicroBuild(.{
83+
.rp2xxx = true,
84+
}).init(b, mz_dep) orelse return;
85+
86+
generate_test_data_run.addFileArg(blk: {
87+
break :blk mb.add_firmware(.{
88+
.name = "test_program.dwarf32",
89+
.root_source_file = b.path("tests/test_program.zig"),
90+
.optimize = .Debug,
91+
.target = mb.ports.rp2xxx.boards.raspberrypi.pico2_arm,
92+
.dwarf_format = .@"32",
93+
}).get_emitted_elf();
94+
});
95+
96+
generate_test_data_run.addFileArg(blk: {
97+
break :blk mb.add_firmware(.{
98+
.name = "test_program.dwarf64",
99+
.root_source_file = b.path("tests/test_program.zig"),
100+
.optimize = .Debug,
101+
.target = mb.ports.rp2xxx.boards.raspberrypi.pico2_arm,
102+
.dwarf_format = .@"64",
103+
}).get_emitted_elf();
104+
});
105+
}
106+
107+
const generate_test_results_step = b.step("generate-test-data", "regenerate test data");
108+
generate_test_results_step.dependOn(&generate_test_data_run.step);
109+
110+
const run_tests_run = b.addRunArtifact(test_exe);
111+
const run_tests_step = b.step("test", "test printer");
112+
run_tests_step.dependOn(&run_tests_run.step);
113+
}

tools/printer/build.zig.zon

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.{
2+
.name = .tools_printer,
3+
.version = "0.0.0",
4+
.fingerprint = 0x72009d00e5692d30,
5+
.minimum_zig_version = "0.14.1",
6+
.dependencies = .{
7+
.serial = .{
8+
.url = "git+https://github.com/ZigEmbeddedGroup/serial#ad338133eda8d86332718bbdc58c641a66307c84",
9+
.hash = "serial-0.0.1-PoeRzI20AABcp4FffI6HXKOL6LcGPKmeqfN4Bna4-YYm",
10+
},
11+
// use to compile the test program
12+
.microzig = .{ .path = "../..", .lazy = true },
13+
},
14+
.paths = .{
15+
"build.zig",
16+
"build.zig.zon",
17+
"README.md",
18+
"src",
19+
"examples",
20+
"tests",
21+
},
22+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const std = @import("std");
2+
const printer = @import("printer");
3+
const serial = @import("serial");
4+
5+
pub fn main() !void {
6+
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
7+
defer _ = debug_allocator.deinit();
8+
const allocator = debug_allocator.allocator();
9+
10+
const args = try std.process.argsAlloc(allocator);
11+
defer std.process.argsFree(allocator, args);
12+
13+
if (args.len != 4) return error.UsageError;
14+
15+
const elf_path = args[1];
16+
const serial_device_path = args[2];
17+
const baud_rate = try std.fmt.parseInt(u32, args[3], 10);
18+
19+
const elf_file = try std.fs.cwd().openFile(elf_path, .{});
20+
defer elf_file.close();
21+
22+
var elf = try printer.Elf.init(allocator, elf_file);
23+
defer elf.deinit(allocator);
24+
25+
var debug_info = try printer.DebugInfo.init(allocator, elf);
26+
defer debug_info.deinit(allocator);
27+
28+
var annotator: printer.Annotator = .init;
29+
30+
const serial_device = try std.fs.cwd().openFile(serial_device_path, .{});
31+
defer serial_device.close();
32+
try serial.configureSerialPort(serial_device, .{
33+
.baud_rate = baud_rate,
34+
});
35+
try serial.flushSerialPort(serial_device, .both);
36+
37+
{
38+
var flash_cmd: std.process.Child = .init(&.{
39+
"picotool",
40+
"load",
41+
"-u",
42+
"-v",
43+
"-x",
44+
"-t",
45+
"elf",
46+
elf_path,
47+
}, allocator);
48+
const term = try flash_cmd.spawnAndWait();
49+
switch (term) {
50+
.Exited => |code| if (code != 0) return error.FlashCommandFailed,
51+
else => {},
52+
}
53+
}
54+
55+
const stdout = std.io.getStdOut();
56+
const out_stream = stdout.writer();
57+
const out_tty_config = std.io.tty.detectConfig(stdout);
58+
59+
var buf: [4096]u8 = undefined;
60+
while (true) {
61+
const n = try serial_device.read(&buf);
62+
try annotator.process(buf[0..n], elf, &debug_info, out_stream, out_tty_config);
63+
}
64+
}

0 commit comments

Comments
 (0)