Introduction
Aerology is a special purpose debugger for inspecting snapshots of Zephyr and TFM Applications.
At the time of writing it is able to provide convinient access to:
- memory layout, with the
segments
subcommand - Zephyr DTS, with the
dts
subcommand - Zephyr Config, with the
command
subcommand - stack usage by thread, with the
stacks
subcommand - backtraces by thread, with the
backtrace
subcommand - and general purpose queries beginning with global variables, with the
query
subcommand
This book in structured as a case study of a particular bug, and will assume familiarity with both Zephyr and TF-M, where appropriate.
Installation
Aerology is written in Rust, and uses the capstone library for disassembly. Since capstone is written in C++, you will need to ensure that you have a suitable compiler instaled.
If a recent version of Rust in not provided by your OS or it's package manager you may find rustup.rs helpful.
Like many Rust applications, Aerology is built with Rust's build tool and package manager Cargo. Installation is also handled by Cargo, and
$ cargo install --path .
should be sufficient to install Aerology.
Debugging Prep
Aerology's debugging subcommands operate primarily on an application snapshot, called a core, and provides a means of dumping a core.
At the moment, Taking a core is a two step process. If this is found to be too comberson, it may be changed.
Aerology builds a single-file application package, called a Zephyr App
Package or ZAP for short, from a Zephyr build directory.
Aerology takes snapshots using this ZAP.
Some subcommands, such as segments
, can work with both a zap and a core.
Packing a ZAP
Aerology builds a ZAP with the pack
subcommand.
This subcommand requires a build directory and also accepts an optional zap file
name.
The usage is:
aerology pack <BUILD_DIRECTORY> [OUT_FILE]
For example, I could package my version of the net/dhcpv4_client
sample built
for the lpcxpresso55s69_ns
platform with the following command:
# Verify that this is indeed a zephyr build directory
$ ls build/lpcxpresso55s69_ns/net/dhcpv4_client/
app compile_commands.json tfm_s_signed.hex
build.ninja Kconfig zephyr
cmake_install.cmake modules zephyr_modules.txt
CMakeCache.txt tfm zephyr_ns_signed.hex
CMakeFiles tfm_merged.hex zephyr_settings.txt
# Package the build directory
$ aerology pack build/lpcxpresso55s69_ns/net/dhcpv4_client/
packed into "dhcpv4_client.zap"
As may be evedent from the above shell session, when no output file is specified, Aerology infers a name from the last component of the build directory.
We can test that this is a working zap by running the segments
subcommand.
Don't worry about what the output looks like for now, as we'll cover that in
another chapter.
What we're looking for is a lack of errors:
$ aerology segments -s dhcpv4_client.zap
Note: Not to scale.
Key: r = readable, w = writable, x = executable, z = zeroed on startup
┃ = overlapping section
zephyr tfm_s bl2
300274e0┌──────────┐
│ rwz│
300222a0└──────────┘
3002229c┌──────────┐
│ rw│
30022000└──────────┘
3000ce64 ┌──────────┐
│ rwz│
3000b5fc ┢━━━━━━━━━━┪
┃ rw┃
3000b500 ┡━━━━━━━━━━┩
30007d60 │ │┌──────────┐
│ ││ rwz│
30005560 │ │└──────────┘
30005558 │ │┌──────────┐
│ ││ rw│
30002640 ┢━━━━━━━━━━┪│ │
┃ rw┃│ │
300025c0 ┡━━━━━━━━━━┩│ │
30000840 ┢━━━━━━━━━━┪│ │
┃ rw┃│ │
30000820 ┡━━━━━━━━━━┩│ │
30000400 │ │├──────────┤
│ ││ rwz│
30000000 └──────────┘└──────────┘
10044024┌──────────┐
│ rwx│
10030000└──────────┘
1001df20 ┌──────────┐
│ rx│
100113e0 └──────────┘
1001133c ┌──────────┐
│ rwx│
100060fc │ │┌──────────┐
│ ││ rwx│
10000000 └──────────┘└──────────┘
No errors, so it looks like our ZAP is fine.
Dumping a Core
Now that we have a working ZAP, we can use it to dump a core.
Aerology exposes the dump
subcommand to dump a core using the information
in a ZAP.
The usage f the dump command is:
aerology dump <ZAP_FILE> [CORE_FILE]
Similar to the pack
subcommand, the output file argument is optional, and
it will infer a name based on the ZAP passed as the first argument.
Also similar to pack
, there is very little output.
$ aerology dump dhcpv4_client.zap
Wrote core dump to "dhcpv4_client.core.0"
The core dump that Aerology wrote is self-contained, and can be transfered across machines with different archetectures and operating systems without losing any context or having an different behavior.
Config, Segments, Device Tree
Aerology stores the Zephyr Config and Device Tree in both ZAPs and cores. These files are accessable with a few subcommands.
Config, the config
subcommand
The config
subcommand dumps the Kconfig that was used for the build of
Zephyr in the ZAP or core.
For example:
$ aerology config dhcpv4_client.zap
--- Lots of config output ---
#
# Boot Options
#
# CONFIG_IS_BOOTLOADER is not set
# CONFIG_BOOTLOADER_MCUBOOT is not set
# CONFIG_BOOTLOADER_BOSSA is not set
# end of Boot Options
#
# Compatibility
#
CONFIG_COMPAT_INCLUDES=y
# end of Compatibility
Further, the config may be queried from a core dump as well. If the core dump and the ZAP have been built from the same build of Zephyr, their configs will be the same.
$ diff <(aerology config dhcpv4_client.zap) <(aerology config dhcpv4_client.core.0)
--- No Output ---
Device Tree, the dts
subcommand
Similar to the config
subcommand, the dts
subcommand dumps the device
tree source used in the build.
For example:
$ aerology dts dhcpv4_client.core.0
--- Lots of DTS output ---
reserved-memory {
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
ranges;
code: memory@01060000 {
reg = < 0x1060000 0x60000 >;
};
ram: memory@21000000 {
reg = < 0x21000000 0x200000 >;
};
};
};
As with the config
subcommand, the dts
subcommand works on both ZAPs
and cores.
Memory Layout, the segments
subcommand
The memory layout of a Zephyr (and TFM) application can be visually inspected
with the segments
subcommand:
$ aerology segments --summary dhcpv4_client.core.0
Note: Not to scale.
Key: r = readable, w = writable, x = executable, z = zeroed on startup
┃ = overlapping section
zephyr tfm_s bl2
31007928┌──────────┐
│ rwz│
31000418└──────────┘
31000414┌──────────┐
│ rw│
31000000└──────────┘
3000ff70 ┌──────────┐
│ rwz│
3000bf38 ┢━━━━━━━━━━┪
┃ rw┃
3000bbc0 ┡━━━━━━━━━━┩
30005f40 │ │┌──────────┐
│ ││ rw│
30003ac0 ┢━━━━━━━━━━┪│ │
┃ rw┃│ │
30003a80 ┡━━━━━━━━━━┩│ │
30002020 ┢━━━━━━━━━━┪│ │
┃ rw┃│ │
30002000 ┡━━━━━━━━━━┩│ │
30000400 │ │├──────────┤
│ ││ rwz│
30000000 └──────────┘└──────────┘
11076384┌──────────┐
│ rwx│
11060000└──────────┘
1105f500 ┌──────────┐
│ rx│
1105f4c0 └──────────┘
11021280 ┌──────────┐
│ rx│
11009fe0 └──────────┘
11009f68 ┌──────────┐
│ rwx│
11000000 └──────────┘
100055e0 ┌──────────┐
│ rwx│
10000000 └──────────┘
The above shows the --summary
output, because the unsummarized output is
quite long.
Without summary it looks like:
$ aerology segments dhcpv4_client.core.0
Note: Not to scale.
Key: r = readable, w = writable, x = executable, z = zeroed on startup
┃ = overlapping section
zephyr tfm_s bl2
31007928┌──────────────────────────────┐
│noinit rwz│
31002020├──────────────────────────────┤
│bss rwz│
31000418└──────────────────────────────┘
31000414┌──────────────────────────────┐
│net_l2_area r│
31000404├──────────────────────────────┤
│net_if_dev_area rw│
310003e8├──────────────────────────────┤
│net_if_area rw│
310003a8└──────────────────────────────┘
310003a4┌──────────────────────────────┐
│_net_buf_pool_area rw│
3100034c├──────────────────────────────┤
│k_sem_area rw│
310002ec├──────────────────────────────┤
│k_msgq_area rw│
310002bc├──────────────────────────────┤
│k_mutex_area rw│
3100026c├──────────────────────────────┤
│k_mem_slab_area rw│
31000234├──────────────────────────────┤
│log_dynamic_sections rw│
310001d0├──────────────────────────────┤
│device_states rw│
31000188└──────────────────────────────┘
31000187┌──────────────────────────────┐
│datas rw│
31000000└──────────────────────────────┘
3000ff70 ┌──────────────────────────────┐
│.TFM_BSS rwz│
3000bf40 └──────────────────────────────┘
3000bf38 ┌──────────────────────────────┐
│.TFM_DATA rw│
3000bbc0 ├──────────────────────────────┤
│.TFM_PSA_ROT_LINKER_BSS rwz│
30005f40 │ │┌──────────────────────────────┐
│ ││.heap rwz│
30004f40 │ │├──────────────────────────────┤
│ ││.msp_stack rwz│
30003ac0 ├──────────────────────────────┤│ │
│.TFM_PSA_ROT_LINKER_DATA rw││ │
30003a80 ├──────────────────────────────┤│ │
│.TFM_APP_ROT_LINKER_BSS rwz││ │
30003740 │ │└──────────────────────────────┘
30003728 │ │┌──────────────────────────────┐
│ ││.bss rwz│
30002020 ├──────────────────────────────┤│ │
│.TFM_APP_ROT_LINKER_DATA rw││ │
30002000 ├──────────────────────────────┤│ │
│.heap rwz││ │
30001000 ├──────────────────────────────┤│ │
│.psp_stack rwz││ │
30000800 ├──────────────────────────────┤│ │
│.msp_stack rwz││ │
30000494 │ │├──────────────────────────────┤
│ ││.data rw│
30000400 ├──────────────────────────────┤├──────────────────────────────┤
│.tfm_bl2_shared_data rwz││.tfm_bl2_shared_data rwz│
30000000 └──────────────────────────────┘└──────────────────────────────┘
11076384┌──────────────────────────────┐
│rodata r│
11071bfc├──────────────────────────────┤
│device_handles r│
11071b90├──────────────────────────────┤
│shell_root_cmds_sections r│
11071b38├──────────────────────────────┤
│shell_area r│
11071b08├──────────────────────────────┤
│log_backends_sections r│
11071af8├──────────────────────────────┤
│log_const_sections r│
11071a30├──────────────────────────────┤
│sw_isr_table rw│
11071630├──────────────────────────────┤
│devices r│
11071480├──────────────────────────────┤
│initlevel r│
110713a0├──────────────────────────────┤
│.ARM.exidx r│
11071398├──────────────────────────────┤
│text rx│
11060640├──────────────────────────────┤
│rom_start rwx│
11060000└──────────────────────────────┘
1105f500 ┌──────────────────────────────┐
│.gnu.sgstubs rx│
1105f4c0 └──────────────────────────────┘
11021280 ┌──────────────────────────────┐
│.ARM.exidx r│
11021278 ├──────────────────────────────┤
│.psa_interface_thread_call rx│
11021180 ├──────────────────────────────┤
│.TFM_UNPRIV_CODE rx│
11009fe0 └──────────────────────────────┘
11009f68 ┌──────────────────────────────┐
│.ER_TFM_CODE rx│
110056c0 ├──────────────────────────────┤
│.TFM_APP_ROT_LINKER rx│
11004940 ├──────────────────────────────┤
│.TFM_PSA_ROT_LINKER rx│
11000f40 └──────────────────────────────┘
11000f24 ┌──────────────────────────────┐
│.TFM_SP_LOAD_LIST r│
11000d34 ├──────────────────────────────┤
│.zero.table rw│
11000d1c ├──────────────────────────────┤
│.copy.table rw│
11000cf8 ├──────────────────────────────┤
│.TFM_VECTORS rx│
11000400 └──────────────────────────────┘
100055e0 ┌──────────────────────────────┐
│.zero.table rw│
100055d0 ├──────────────────────────────┤
│.copy.table rw│
100055b8 ├──────────────────────────────┤
│.ARM.exidx r│
100055b0 ├──────────────────────────────┤
│.text rx│
10000000 └──────────────────────────────┘
Stacks
Aerology currently supports 2 subcommands that help with looking through
stacks: stacks
for a summary of the filled-level of all thread stacks
Aerology can find, and backtrace
for backtracing all stacks in the system.
stacks
subcommand
The stacks subcommand renders a pretty version of stack usage in a table format:
$ aerology stacks dhcpv4_client.core.0
Key: █: currently in use ▒: used in the past ░: never used
name used max size
zephyr::logging 32b 32b 768b ░░░
zephyr::shell_uart 32b 32b 2048b ░░░░░░░░
zephyr::idle 00 32b 32b 320b ░
zephyr::main 88b 216b 1024b ░░░░
tfm_s::3000bf40 312b 2440b 8192b █▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░
tfm_s::3000bf88 192b 192b 2688b ░░░░░░░░░░░
tfm_s::3000bfd0 168b 776b 2048b ▒▒▒░░░░░
tfm_s::3000c018 176b 644b 1664b ▒▒░░░░░
tfm_s::3000c060 120b 256b 1280b ▒░░░░
tfm_s::3000c0a8 72b 300b 1024b ▒░░░
tfm_s::3000c0f0 72b 20b 256b ░
This might be helpful for determining the maximum stack usage of a given thread, for example.
backtrace
subcommand
The backtrace subcommand is where Aerology really shines; it backtraces all threads in the system including stacks of TFM partitions:
$ aerology backtrace dhcpv4_client.core.0
Registers
├─ 11008a60 in tfm_access_violation_handler
╞═ Exception Handler Called
├─ 01069bfc in smsc_init
├─ 01069ce3 in eth_init
├─ 01069fe5 in z_sys_init_run_level
├─ 0106a1af in bg_thread_main
├─ 0106bae1 in z_thread_entry
└─ 010645e3 in arch_switch_to_main_thread
Thread zephyr::idle 00
├─ 0106bad4 in z_thread_entry
└─ aaaaaaaa in <unknown>
Thread zephyr::logging
├─ 0106bad4 in z_thread_entry
└─ aaaaaaaa in <unknown>
Thread zephyr::main
├─ 010644f8 in arch_swap
├─ 0106b8dd in k_sys_work_q_init
├─ 01069fe5 in z_sys_init_run_level
├─ 0106a1af in bg_thread_main
├─ 0106bae1 in z_thread_entry
└─ 010645e3 in arch_switch_to_main_thread
Thread zephyr::shell_uart
├─ 0106bad4 in z_thread_entry
└─ aaaaaaaa in <unknown>
Thread tfm_s::sp_crypto
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
Thread tfm_s::sp_initial_attestation
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
Thread tfm_s::sp_ps
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
Thread tfm_s::sp_its
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
Thread tfm_s::sp_platform
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
Thread tfm_s::sp_ns_agent
└─ 11008c85 in tfm_nspm_thread_entry
Thread tfm_s::idle
└─ 11008ca5 in tfm_idle_thread
Optionally, you can have Aerology dump the registers at each stack frame with
the --regs
.
The following in an excert from the same backtrace with -r
applied:
Thread zephyr::main
├─ 010644f8 in arch_swap
│ R0 00000000 R1 01071470 R2 01071618 R3 01069ce3
│ R4 00000000 R5 00000000 R6 01071480 R7 0106a1a1
│ R8 01064cd8 R9 01064cd8 R10 01064cd8 R11 01064cd8
│ R12 00000000 Sp 210037c8 Lr 0106b8dd Pc 010644f8
│ Psr 61000000
├─ 0106b8dd in k_sys_work_q_init
│ R0 00000000 R1 01071470 R2 01071618 R3 01069ce3
│ R4 01071470 R5 00000000 R6 01071480 R7 0106a1a1
│ R8 01064cd8 R9 01064cd8 R10 01064cd8 R11 01064cd8
│ R12 00000000 Sp 210037e0 Lr 01069fe5 Pc 0106b8dd
│ Psr 61000000
├─ 01069fe5 in z_sys_init_run_level
│ R0 00000000 R1 01071470 R2 01071618 R3 01069ce3
│ R4 0106a1a1 R5 21000e98 R6 21003800 R7 0106a1a1
│ R8 01064cd8 R9 01064cd8 R10 01064cd8 R11 01064cd8
│ R12 00000000 Sp 210037f0 Lr 0106a1af Pc 01069fe5
│ Psr 61000000
├─ 0106a1af in bg_thread_main
│ R0 00000000 R1 01071470 R2 01071618 R3 00000000
│ R4 0106a1a1 R5 21000e98 R6 21003800 R7 0106a1a1
│ R8 01064cd8 R9 01064cd8 R10 01064cd8 R11 01064cd8
│ R12 00000000 Sp 210037f8 Lr 0106bae1 Pc 0106a1af
│ Psr 61000000
├─ 0106bae1 in z_thread_entry
│ R0 00000000 R1 01071470 R2 01071618 R3 00000000
│ R4 0106a1a1 R5 21000e98 R6 21003800 R7 0106a1a1
│ R8 01064cd8 R9 01064cd8 R10 01064cd8 R11 01064cd8
│ R12 00000000 Sp 21003800 Lr 010645e3 Pc 0106bae1
│ Psr 61000000
└─ 010645e3 in arch_switch_to_main_thread
R0 00000000 R1 01071470 R2 01071618 R3 00000000
R4 0106a1a1 R5 21000e98 R6 21003800 R7 0106a1a1
R8 01064cd8 R9 01064cd8 R10 01064cd8 R11 01064cd8
R12 00000000 Sp 21003800 Lr 010645e3 Pc 010645e3
Psr 61000000
Since each stack frame takes up 6 lines instead of 1, all of the backtraces together becomes quite long.
Queries
Aerology's query
subcommand is easily it's most general purpse tool.
It provides a means to inspect arbitrary objects (e.g. structs, unions, etc.)
in a C-style fromat or a hexdump.
Syntax
The syntax of a query is inspired by jq
, and many of the operators are postfix.
An Aerology query is structured as a global, with optional postfix operators,
followed by zero or more =>
separated "filters".
Global
Unlike jq
, the initial value is provided by global optionally prefixed with a
executable namespace.
For example, to get the zephyr kernel struct, either of the following queries
produce the same result:
_kernel
zephyr::kernel
Executing a query with just the global prints the whole structure C-style:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel
21001ec0: (struct z_kernel) {
.cpus = {
(struct _cpu) {
.nested = (uint32_t) 0 /*0x0*/,
.irq_stack = (char *) 553664832 /*0x21004140*/,
.current = (struct k_thread *) 553651864 /*0x21000e98*/,
.idle_thread = (struct k_thread *) 553651680 /*0x21000de0*/,
.slice_ticks = (int) 0,
.id = (uint8_t) 0 /*0x0*/,
/* .arch is Missing */
},
},
.ready_q = (struct _ready_q) {
.cache = (struct k_thread *) 553651864 /*0x21000e98*/,
.runq = (sys_dlist_t) {
.head = (struct _dnode *) 553651864 /*0x21000e98*/,
.next = (struct _dnode *) 553651864 /*0x21000e98*/,
.tail = (struct _dnode *) 553650440 /*0x21000908*/,
.prev = (struct _dnode *) 553650440 /*0x21000908*/,
},
},
.threads = (struct k_thread *) 553652056 /*0x21000f58*/,
}
Postfix Operators
Once a global is selected, you may follow members with postfix operators. All postfix operators expect a type class (struct, array, pointer, etc.), and will error when the type at that step of the query is of a different class.
The operators are:
.<struct-member>
to traverse a struct into a member named "struct-member". This will derefrence pointers to structures before atempting to move to a member if the current type is a pointer to a structure. This will error when the struct does not contain the required member.[<number>]
will select array member with index "number". Similar to the struct member operation, this will dereference pointers to arrays before atempting to select the member. This will error if the index is out of bounds, or the array has no size.[]
will select all array members, treating the remainder of the query as a query over all members of the array. This will error if the array has no size..*
postfix derefrence operator. This will derefrence a pointer.
Struct Member
Continuing with the Zephyr kernel structure, we may select the head of the threads linked list as follows:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.threads
21001ee4: (struct k_thread *) 553652056 /*0x21000f58*/
If we were to have a typo, we would get an error such as:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.thread
Error:
× member not found "thread"
┌─[command-line:1:1]
1 │ zephyr::_kernel.thread
· ───┬──
· └── missing
└────
help: consider replacing with "cpus", "ready_q" or "threads" instead
If we try to select a member of something that's not a struct, such as the cpu array, we get a type error such as:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.cpus.nested
Error:
× type mismatch
┌─[command-line:1:1]
1 │ zephyr::_kernel.cpus.nested
· ───┬──
· └── expected struct or union found struct _cpu[1]
└────
Members may be selected through poiters to structures, without an intevening
dereference operator.
For exmple, if we wanted to select the arch
member of the thread struct at
the head of the threads linked list, we may use the following query:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.threads
21001ee4: (struct k_thread *) 553652056 /*0x21000f58*/
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.threads.arch
21001004: (struct _thread_arch) {
.basepri = (uint32_t) 0 /*0x0*/,
.swap_return_value = (uint32_t) 4294967285 /*0xfffffff5*/,
.mode = (uint32_t) 48128 /*0xbc00*/,
.mode_bits = (uint8_t) 0 /*0x0*/,
.mode_exc_return = (uint8_t) 188 /*0xbc*/,
.mode_reserved2 = (uint16_t) 0 /*0x0*/,
}
Note that above the type of zephyr::_kernel.threads
is a struct k_thread *
,
but we used it as if it were a struct k_thread
.
Array Index
Selecting an index in an array works similar to selecting a struct member. For example if we wanted to select the first entry (index 0) of the Zephyr kernel's cpu array we may:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.cpus[0]
21001ec0: (struct _cpu) {
.nested = (uint32_t) 0 /*0x0*/,
.irq_stack = (char *) 553664832 /*0x21004140*/,
.current = (struct k_thread *) 553651864 /*0x21000e98*/,
.idle_thread = (struct k_thread *) 553651680 /*0x21000de0*/,
.slice_ticks = (int) 0,
.id = (uint8_t) 0 /*0x0*/,
/* .arch is Missing */
}
However, if we index past the end of the array, we get an error:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.cpus[11110]
Error:
× Array index out of bounds
┌─[command-line:1:1]
1 │ zephyr::_kernel.cpus[11110]
· ───────
└────
help: This array has a size of 1
Further if we try to index something other than an array, we get a type error such as:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel[6]
Error:
× type mismatch
┌─[command-line:1:1]
1 │ zephyr::_kernel[6]
· ─┬─
· └── expected array found struct z_kernel
└────
Whole Array
You may select a whole array by omitting an array index. For example, we may print the value of the char array that makes up a thread's name with the query:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.threads.name[]
21000fd0: (char) 115 /*0x73*/
21000fd1: (char) 121 /*0x79*/
21000fd2: (char) 115 /*0x73*/
21000fd3: (char) 119 /*0x77*/
21000fd4: (char) 111 /*0x6f*/
21000fd5: (char) 114 /*0x72*/
21000fd6: (char) 107 /*0x6b*/
21000fd7: (char) 113 /*0x71*/
21000fd8: (char) 0 /*0x0*/
21000fd9: (char) 0 /*0x0*/
21000fda: (char) 0 /*0x0*/
21000fdb: (char) 0 /*0x0*/
21000fdc: (char) 0 /*0x0*/
21000fdd: (char) 0 /*0x0*/
21000fde: (char) 0 /*0x0*/
21000fdf: (char) 0 /*0x0*/
21000fe0: (char) 0 /*0x0*/
21000fe1: (char) 0 /*0x0*/
21000fe2: (char) 0 /*0x0*/
21000fe3: (char) 0 /*0x0*/
21000fe4: (char) 0 /*0x0*/
21000fe5: (char) 0 /*0x0*/
21000fe6: (char) 0 /*0x0*/
21000fe7: (char) 0 /*0x0*/
21000fe8: (char) 0 /*0x0*/
21000fe9: (char) 0 /*0x0*/
21000fea: (char) 0 /*0x0*/
21000feb: (char) 0 /*0x0*/
21000fec: (char) 0 /*0x0*/
21000fed: (char) 0 /*0x0*/
21000fee: (char) 0 /*0x0*/
21000fef: (char) 0 /*0x0*/
This query is a tad silly, as aerology is capable of printing strings directly:
$ aerology query dhcpv4_client.core.0 zephyr::_kernel.threads.name
21000fd0: (char[32]) "sysworkq"
Filters
At the moment, 3 filters are supported:
- Postfix operators.
- Reading a linked list with
llnodes
- Creating a bactrace with
bt
Postfix operators
Postfix operators may be used as a filter by themselves.
Using an example from the prior section, we may query the name of the head of
the thread list including the filter separator =>
in a few more places.
$ aerology query dhcpv4_client.core.0 'zephyr::_kernel => .threads.name'
21000fd0: (char[32]) "sysworkq"
$ aerology query dhcpv4_client.core.0 'zephyr::_kernel => .threads => .name'
21000fd0: (char[32]) "sysworkq"
$ aerology query dhcpv4_client.core.0 'zephyr::_kernel.threads => .name'
o21000fd0: (char[32]) "sysworkq"
As may be seen in the exmaple above, these produce the same result.
Linked List Nodes
An important filter in Aerology is the linked list nodes reader.
It's invoked llnodes <postfix-member>
.
For example, to get the names of all of the threads in the system, we may
use the following query:
$ aerology query dhcpv4_client.core.0 'zephyr::_kernel.threads => llnodes .next_thread => .name'
21000e58: (char[32]) "idle 00"
210008c8: (char[32]) "logging"
21000f10: (char[32]) "main"
21000980: (char[32]) "shell_uart"
21000fd0: (char[32]) "sysworkq"
Backtrace
Another interesting Aerology filter is the backtrace filter. This filter has a more complex syntax:
bt <reg-name>=<postfix-member>
This filter backtraces after populating the initial register state as described by its arguments.
For example, we can take a backtrace of every thread in TFM with the following query:
$ aerology query dhcpv4_client.core.0 'tfm_s::partition_listhead => llnodes .next => bt pc=.ctx_ctrl.exc_ret psp_s=.ctx_ctrl.sp'
3000bfcc:
╞═ Exception Handler Called
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
3000c014:
╞═ Exception Handler Called
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
3000c05c:
╞═ Exception Handler Called
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
3000c0a4:
╞═ Exception Handler Called
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
3000c0ec:
╞═ Exception Handler Called
├─ 11008e70 in tfm_arch_trigger_pendsv
├─ 11007ee3 in spm_interface_thread_dispatcher
└─ 11021187 in psa_interface_unified_abi
3000c134:
╞═ Exception Handler Called
└─ 11008c85 in tfm_nspm_thread_entry
3000c6e4:
╞═ Exception Handler Called
└─ 11008ca5 in tfm_idle_thread
Note: Zephyr does not store the entire exception payload, so without bitwise operations, we cannot construct a valid value for the pc.
Note: we place the exception payload in pc, not the lr.