Each kernel takes a different form (linked-list task table / compile-time device graph / global symbol environment / user-space server / build-time configuration), but what they share is:The registration model determines what you can see— a function "having no direct cross-reference" can mean, on different RTOSes, respectively "normal (linker registration)", "normal (load-time symbol resolution)", "normal (user-space server)", or "genuinely unreferenced".
So the order is always:First determine this RTOS's execution and registration model, then talk about tasks and kernel objects. </CORE RULE>
Execution model differences (not synonymous substitutions)
| Platform | Where the "driver/service" is | Registration and discovery mechanism | What to determine first |
|---|---|---|---|
| FreeRTOS / ThreadX / RT-Thread | Same image as the kernel; task + kernel object table | Explicit creation API + static object instances | Task table/TCB (this main document) |
| Zephyr | Same image as the kernel, butthe device graph is generated at compile time | devicetree + Kconfig + iterable sections、SYS_INIT |
Whether it is linker-registered ([[zephyr]]) |
| VxWorks | Two kinds: DKM kernel mode / RTP user mode | DKM dynamic loading + global symbol environment; RTP follows standard ELF/.so |
First distinguish DKM or RTP ([[vxworks]]) |
| QNX | User-space server; the network driver is a shared object within io-pkt | pathname registration + message passing | First determine the form ([[qnx]]) |
| RTEMS | Co-located with the kernel/applicationsingle address space | Build-time configuration (confdefs.h) + device driver table + runtime linker |
Configuration and driver table ([[rtems]]) |
| INTEGRITY / ARINC 653 / VxWorks 653 / PikeOS | Partition(three domains: space + time + resources) | Static configuration; partition-level major frame / partition window + intra-partition scheduling | First align the partition window and partition mode([[partitioned-rtos]]) |
| NuttX | Same image as the kernel or separate,depending on the build method | Determined by the build model: FLAT direct call / PROTECTED via syscall proxy / KERNEL via MMU | First determine FLAT / PROTECTED / KERNEL([[nuttx]]) |
| eCos | Same image as the kernel; interrupt handling is split into three layers | ISR → DSR → thread, each layer has a different synchronization mechanism | Determine which layer the work falls on ([[ecos]]) |
| ThreadX Modules | Resident kernel + dynamically loadable modules | module preamble + request ID via software dispatch | First recognize the module ABI ([[threadx-modules]]) |
| FreeRTOS (context) | Same image as the kernel; interrupt context is special | task API and FromISR API are separated | First determine the context ([[freertos-context]]) |
| µC/OS、SYS/BIOS | Same image as the kernel;interrupts participate in scheduling | ISR explicitly notifies the kernel (entry/exit); layered threads Hwi > Swi > Task | Scheduling timing must account for interrupt entry/exit ([[ucos-sysbios]]) |
| SAFERTOS、CMSIS-RTX5 | Same image as the kernel;with access control and safety levels | per-task regions/protection domains, API and object access policies, ISR deferred queue | First determine "who can access what" ([[safertos-rtx5]]) |
| T-Kernel、TOPPERS | Same image as the kernel;Legality of service calls depends on context | Static configuration generates tasks/handlers; device drivers run partially as quasi-tasks | First recover the context type and specification family ([[tkernel-toppers]]) |
| OSE / OSEck | Same image as the kernel butmessages can cross nodes | Message-based IPC + distributed transparent transport layer (shared memory/DMA/interconnect) | First determine whether the message peer is local ([[ose-oseck]]) |
| Nucleus | Same image as the kernel;MMU/MPU subsystem isolation + linear memory mapping | Dynamic reload/restart/update of applications and kernel modules (without downtime) | "A module is replaced but the system keeps running" and "isolation exists but VA does not look per-process independent" are not anomalies |
Failure Mode Decision Table (the main entry point for this skill)
When "no call relationship can be found", "this code looks dead", or "the module cannot be loaded", use this table to suspect first, then rule out:
| Symptom | Suspect first | Handling direction |
|---|---|---|
| Some init / device objecthas no direct xref | Registration mechanism: iterable section / SYS_INIT / DEVICE_DEFINE(Zephyr) |
Adjudicate in the linker section and map file,do not mark it dead code([[zephyr]]) |
| A whole blockregularly arrangedof function pointers or structs | Same as above: a linker-generated registry | First understand it as a registry, then consider vtable / jump table / obfuscation ([[zephyr]]) |
| Kernel services are not called in early init | PRE_KERNEL_* stage services are not yet available (by design) |
Do not conclude "it was trimmed"; conversely, if threads/mutexes/sleep appear, first suspect the stage was misjudged ([[zephyr]]) |
| A processcontrols hardware without a kernel module | QNX user-space resource manager | Look for resmgr_attach / dispatch_* / MsgReceive and the registered pathname ([[qnx]]) |
| Network driveris neither a process nor a module | loaded by io-pkt devnp-*.so |
Find the io-pkt that loads it, and confirm whether it is native or an old driver via a shim ([[qnx]]) |
| Thread priority during samplingchanges suddenly | QNX message-drivenpriority inheritance / server boost | It is not actively calling pthread_setschedparam([[qnx]]) |
| Threadblocks on IPC for a long time | MsgSend is inherently a synchronous wait (normal state) |
Find "who should reply"; pulse is the non-blocking kind ([[qnx]]) |
| In an independent modulea large number of unresolved symbols | VxWorks DKM links tothe target image's global symbol environment | First locate the image and VSB/VIP configuration, do not conclude malformed ([[vxworks]]) |
| The moduleon the same version does not work on VxWorks | The specific image configuration is different | Check VSB/VIP rather than the version string ([[vxworks]]) |
dlopen() behavioris not like a Unix dynamic library |
The RTEMS runtime linker | relocates into the current address space; object names can be libfoo.a:bar.o([[rtems]]) |
Cannot find the device callback's file_operations |
RTEMS device driver table (major = table index) | Restore rtems_driver_address_table and rtems_io_register_name([[rtems]]) |
| Task count/schedulerCannot find the runtime creation point | RTEMS build-time configuration generation | Find the translation unit that defines CONFIGURE_INIT ([[rtems]]) |
| High-priority threaddoes not run(partitioned system) | Partition-level scheduling: there is currently no execution window / budget for the partition it belongs to, or the partition has not yet entered NORMAL | First align the major frame and partition window, then look at thread priority ([[partitioned-rtos]]) |
| "Writing A B C consecutively only reads C" | Sampling port's overwrite semantics | is only abnormal for queue ports ([[partitioned-rtos]]) |
| Fixed-address function table is mistaken for obfuscation | ABI such as the APEX service table | First check it as a service table ([[partitioned-rtos]]) |
| Decompilation results of the same API are completely different across samples | NuttX's FLAT/PROTECTED/KERNEL build differences | First determine the build model; an extremely short API + immediate trap is an auto-generated syscall proxy ([[nuttx]]) |
| Some address is "user-accessible / user faults" | may come respectively from malloc and kmalloc(dual heap) | Distinguish the user heap from the kernel heap ([[nuttx]]) |
| Cannot find where the hardware is actually operated | NuttX's upper half / lower half layering | The hardware implementation is often in arch/ or boards/, not in drivers/([[nuttx]]) |
| A task-version API is called in an ISR | Context determination or symbol identification is wrong (FreeRTOS) | First determine the task/ISR context and API variant ([[freertos-context]]) |
| Some address is "sometimes accessible, sometimes not" | MPU-restricted taskor the MPU region was changed at runtime (FreeRTOS-MPU) | Check the region configuration and runtime region-change calls ([[freertos-context]]) |
| The ISR just clears the status bit and returns | eCos's DSR performs the real work | Check whether the ISR return value schedules the DSR ([[ecos]]) |
| A large amount of "fill in request ID → jump to common dispatcher" | ThreadX Module's ABI | Build a request ID → service name mapping, do not judge it as obfuscation ([[threadx-modules]]) |
| The code address is not within the module's memory region | XIP module (instructions in Flash, data in RAM) | Do not judge as control-flow hijacking ([[threadx-modules]]) |
| A switch occurs at the end of the ISR / the task is ready but no switch occurs | Interrupt entry and exit participate in scheduling(µC/OS entry and exit protocol; SYS/BIOS's Swi_disable Consequently disabling Task scheduling) |
Count interrupt entry and exit as scheduling timing ([[ucos-sysbios]]) |
| The same address has different accessibility under different tasks | per-task MPU region / protection domain(SAFERTOS、RTX5) | Check the region configuration and privilege level; do not judge it as memory corruption ([[safertos-rtx5]]) |
| The handle does not look like a memory pointer | Indirect object ID + cross-reference table(SAFERTOS ESM) | Do not expect to infer the object layout from it ([[safertos-rtx5]]) |
| An API called inside an ISR does not take effect immediately | ISR FIFO deferred queue(RTX5) | The object state changes only after IRQ exit; this is normal ([[safertos-rtx5]]) |
| The service call is "illegal" or rejected | Context type(T-Kernel task/quasi-task/task-independent; TOPPERS task/non-task/ISR/cyclic/alarm) | Consult the specification's valid-context table; do not infer from the API name ([[tkernel-toppers]]) |
| The peer of the message/signal cannot be found | Messages can cross core / CPU / DSP / node(OSE's distributed transparent transport layer) | Do not assume the peer is local, and do not check only local syscalls ([[ose-oseck]]) |
| A module is replaced but the system keeps running | Nucleus's dynamic reload/update(linear memory mapping + MMU/MPU subsystem isolation) | See the runtime model table; do not first judge it as hijacking |
When to use / when not to use
- Prerequisite:First use feature strings/magic numbers/symbols to confirm which RTOS it is(within this family they are very easily confused with each other) → the RTOS section and easily confused pairs in [[re-analyze/system-fingerprints]]
- Use: MCU/IoT firmware running an RTOS—locate the task table/TCB, split decompilation by task, and recover kernel objects such as queues/semaphores/mutexes/timers
- Use: when what you have is a bare image (no filesystem, no symbols), and you need to find the scheduler and all task entry points from the boot code chain
- Use: commercial RTOS firmware (VxWorks/QNX/RTEMS/INTEGRITY)—in automotive head-unit, avionics, and industrial control scenarios, also starting from locating task/thread control blocks and splitting analysis by process/partition/task
- Use: **"drivers/services do not exist in the form of kernel modules"or"the registration relationship does not hold in the source code"**—first determine the runtime model (user-mode server / global symbol environment / linker registration / build-time configuration), see the branches and decision table
- Do not use: bare-metal firmware (no task table/scheduler; analyze as an ordinary MCU image, [[re-fw-extract]] → [[re-binary-core]])
- Do not use: Linux/Windows kernels (go to [[re-kernel]])
- Do not use: when you only need to run it dynamically to observe behavior (go to [[re-fw-emulate]])
Tool preparation
This skill is mainly based on pure static analysis (sandbox-free, the highest principle of [[re-analyze/platform-tips]]); when dynamic verification is needed, switch to [[re-fw-emulate]]. Tools may be replaced; the method is the core.
Ghidra (the main workhorse for decompilation and struct definition)
- Download: GitHub
NationalSecurityAgency/ghidrareleases, unzip and use (no installation required) - JDK 21 (required by Ghidra 11.3+):
- Linux:
apt install openjdk-21-jdk/dnf install java-21-openjdk/pacman -S jdk21-openjdk - macOS:
brew install openjdk@21orbrew install --cask temurin - Windows:
choco install temurin21
- Linux:
- Launch:
./ghidraRun(GUI)/./support/analyzeHeadless(headless) - Verify:
java -version(must be 21+);./support/analyzeHeadless -helpnormal output - Core usage: after defining TCB/kernel object structs in Data Type Manager, type propagation significantly improves decompilation quality
binwalk (image extraction first)
- For installation and version forms, use the installation matrix in [[re-fw-extract]] "Tool Preparation" as the single source of truth; this skill does not duplicate the package name list
- Verify:
binwalk --version
python3 (scripted parsing and batch annotation)
- Linux:
apt install python3/dnf install python3/pacman -S python - macOS: built in; Windows: official installer or
choco install python - Verify:
python3 --version - Purpose: batch search for signature strings/magic numbers, parse task tables by field offsets, generate Ghidra scripts to automatically annotate task names/entries
Operating Steps
Execute in order, archive the result of each step (task list, kernel object table, Ghidra project).
-
Prerequisite: image extraction and format identification (connects to the [[re-fw-extract]] workflow):
- First complete according to [[re-fw-extract]]: binwalk unpacking / dd splitting by offset /
file+readelf -hconfirm architecture and endianness (big-endian ARM/MIPS firmware is common) - The product is usually a raw binary or a single ELF; record the reset handler entry and RAM base address (inferred from the linker script/startup code) - the task table and TCB are both in the RAM region, interpret them according to the link addresses
- Confirm endianness and architecture before importing into Ghidra, to avoid making the entire analysis useless
- First complete according to [[re-fw-extract]]: binwalk unpacking / dd splitting by offset /
-
RTOS identification: startup code chain + signature string/symbol search:
- Startup chain form: reset handler → clock/peripheral/memory initialization → create tasks (
xTaskCreate/osThreadNew/tx_thread_create/k_thread_create/rt_thread_create/ VxWorkstaskSpawn(commonly used in 6.x and earlier; still exists in 7, internally implemented as taskCreate+taskActivate) ortaskCreate(from 7 onward, creating a suspended task requires explicit taskActivate)/ QNXThreadCreate/ INTEGRITY ARINC 653 APEXCREATE_PROCESS) → scheduler start (vTaskStartScheduler/osKernelStart/tx_kernel_enter/rt_system_scheduler_start/ VxWorkskernelInit/ QNX startup script launches system processes / INTEGRITYSTARTenters running state). The code before the scheduler start call site is all initialization and does not belong to any task - Signature strings: strings search for kernel names ("FreeRTOS"), task names ("Idle", etc.), version/assertion strings; when an ELF is not stripped, search directly for symbols (
nm/ Ghidra Symbol Table:pxCurrentTCB、_tx_thread_created_list、_kernel、rt_thread_ready_priority_group) - Commercial RTOS signature strings: VxWorks task names ("tIdle" idle task (VxWorks 6.x+), "tRootTask" root task) and version strings; QNX kernel/image names ("procnto", "imagefs") and system process names (resource managers commonly use naming
io-*/devb-*/devc-*); INTEGRITY partition names/process names and ARINC 653 APEX service names - ThreadX has no version string to search for; rely on the scheduling entry
tx_kernel_enter+ TX_THREAD magic ID (step 3) to confirm - When unsure of the kernel: find 3-4 task creation call sites and the call form of the scheduling entry, and compare against each kernel's public structures to rule them out one by one
- Startup chain form: reset handler → clock/peripheral/memory initialization → create tasks (
-
Task table location(choose the structure by kernel; after defining the struct in Ghidra, restore priority/state/stack pointer/task name field by field):
- FreeRTOS: Key fields of the TCB (tskTaskControlBlock) — pxTopOfStack (stack top pointer), uxPriority (priority), pcTaskName (task name array, 16 bytes by default), pxStack (stack start; newer versions additionally have pxEndOfStack); the task lists are in the static area — pxReadyTasksLists[number of priorities] (array of lists indexed by priority), xDelayedTaskList1/2, xSuspendedTaskList; the current task pointer pxCurrentTCB. Location method: in the context switch code, find the reads/writes to TCB fields (saving/restoring the stack pointer, updating the current task pointer), and cross-reference back to the static area
- ThreadX:
_tx_thread_created_listGlobal doubly linked list (each TX_THREAD is linked to the others via the created_next/created_previous fields), with_tx_thread_created_countcount; key fields of TX_THREAD — tx_thread_id (magic number 0x54485244 'THRD', used to confirm the structure), tx_thread_name (task name), tx_thread_priority, tx_thread_state, tx_thread_stack_start/end/ptr (stack area), tx_thread_entry (entry function) - Zephyr:
_kernelGlobal structure (in newer versions struct _cpu, in older versions struct _kernel) — current (current thread pointer), ready_q (ready queue: priority bitmap + array of queues indexed by priority), timeout_q (timeout queue), idle thread pointer; there is also a slist global thread chain linking all k_threads; key fields of k_thread — base.prio (priority), stack_info.start/size (stack area), name (thread name) - RT-Thread: rt_thread_ready_priority_group (priority bitmap) + rt_thread_priority_table[RT_THREAD_PRIORITY_MAX] (array of lists indexed by priority); key fields of the rt_thread structure — name, priority, stack_addr/stack_size, entry (entry function)
- VxWorks: Key fields of WIND_TCB — td_name (task name, a string pointer pointing into the string pool, not an inline array), td_sp (saved stack pointer), td_priority (priority 0-255, 0 is highest), td_status, td_options, td_entry (entry function), td_pStackBase/pStackLimit/pStackEnd (stack base/valid stack boundary/actual stack boundary), td_stackSize/td_stackHigh (stack size/historical high-water mark). Note version differences: since VxWorks 7, WIND_TCB is an opaque type, and taskLib.h only provides the VX_WIND_TCB_SIZE size macro; field offsets vary with version/SMP configuration. Location method: the task name string is in the string pool (search for "tIdle"/"tRootTask"), cross-reference back to the TCB that points to it; or infer field offsets from the context switch code (saving the register set to the stack, writing td_sp, selecting the task by td_priority), then follow the task list back to the static area
- QNX: procnto (microkernel + process manager combined) manages thread control blocks — the thread is the smallest scheduling unit, and the process is merely an address space container; key thread attributes — tid (thread number within the process), priority (256 levels, 0 is idle), thread name (supported in 6.3.2+), stack and TLS area (containing tid/pid/stack base/errno) (the internal layout has no officially published definition; attributes are observed via system call parameters and the /proc side channel). Location method: QNX firmware is an IFS image (startup header magic number 0x00ff7eeb + "imagefs" signature); use dumpifs to extract the ELF of each system process and then analyze them one by one (procnto is the kernel body itself); on the application side, start from
ThreadCreate/MsgSendand other kernel call sites to locate threads, and cross-reference the thread name string back to the thread control block; in a dynamic environment, the DCMD_PROC_TIDSTATUS of /proc//ctl can be used to read thread status as a side channel - INTEGRITY: A partition is a space + time isolation unit—spatially, each partition has an independent memory region (enforced by the MMU); temporally, execution windows are allocated cyclically according to a module schedule (one round = major frame); tasks within a partition are ARINC 653 processes, and process control block attributes include entry point, stack size, base priority, period/deadline. Location method: fully static configuration—allocated once at startup, no dynamic memory/no dynamic task creation, object addresses fixed, boot table defines resource ownership; partition/process name strings are readable in the firmware and cross-reference back to the configuration table; ARINC 653 APEX service names (CREATE_PROCESS/SET_PRIORITY/GET_TIME, etc.) call sites are kernel service entry points
- Produce a task list: read out task name/priority/stack range/entry address TCB by TCB, one line per task
-
Task stack identification:
- Each task's TCB has stack region fields: FreeRTOS pxStack/pxEndOfStack, ThreadX tx_thread_stack_start/end, Zephyr stack_info.start/size, RT-Thread stack_addr/stack_size, VxWorks td_pStackBase/pStackLimit/pStackEnd + td_stackSize (default 0xee fill, disabled by the VX_NO_STACK_FILL option), QNX thread stack bounds can be assistively confirmed from the TLS area (stack base/tid/pid), INTEGRITY process stacks are within the partition memory region (stack size is in the process creation attributes)
- Stack region form: a static array in .bss aligned according to configuration macros, or allocated from a memory pool; when FreeRTOS stack overflow checking is enabled, the stack contains a 0xa5 fill pattern, which can assistively confirm the stack range
- Divide task boundaries by stack ownership: the stack range belongs to that task (local variables, nested call chain); during decompilation use "which stack segment belongs to which task" to divide code ownership; the stack pointer value saved/restored on task switch directly corresponds to the TCB's stack top field
-
Kernel objects (queues/semaphores/mutexes/timers):
- Infer backward from kernel API call sites:
xQueueSend/xQueueReceive/xSemaphoreTake/xTimerCreate(FreeRTOS)、tx_queue_send/tx_semaphore_get/tx_mutex_put/tx_timer_create(ThreadX)、k_sem_take/k_mutex_lock/k_queue_get/k_timer_start(Zephyr)、rt_sem_take/rt_mutex_take/rt_mq_recv(RT-Thread)—the first argument at the call site is the object pointer; trace back along cross-references to the object definition (.bss static instance or memory pool) - Commercial RTOS kernel objects: VxWorks semaphores (semGive/semTake), message queues (msgQSend/msgQReceive), events (eventSend/eventReceive), watchdog (wdCreate)—the call site arguments are the object IDs; trace back along cross-references to the static area definition; QNX is a channel/connection model—the MsgReceive thread creates a channel, the MsgSend thread creates a connection, synchronous messages put the thread into SEND/REPLY/RECEIVE blocking states (messages are copied directly across address spaces, with no intermediate buffer), a pulse is a non-blocking notification of 4 bytes of data + 1 byte code; INTEGRITY inter-partition communication is via sampling ports (overwrite semantics)/queuing ports (queuing semantics), and within a partition via semaphores/events/buffers/blackboards, searched by ARINC 653 service names
- ThreadX object headers have magic IDs that can be confirmed one by one (based on official source code; do not rely on remembered references): TX_QUEUE 0x51554555 'QUEU', TX_SEMAPHORE 0x53454D41 'SEMA', TX_MUTEX 0x4D555445 'MUTE', TX_TIMER 0x4154494D 'ATIM', TX_EVENT_FLAGS 0x4456444E 'DVDE', TX_BYTE_POOL 0x42595445 'BYTE', TX_BLOCK_POOL 0x424C4F43 'BLOC' (the TIMR/EVEN notations in older references are inaccurate; do not use them)
- In FreeRTOS, semaphores/mutexes are special cases of queues (Queue_t) (structurally isomorphic), and timers are Timer_t; after defining the structs in Ghidra, argument type propagation at call sites automatically improves decompilation
- Infer object usage from semantics: who sends and who receives, what is processed after take, i.e. the inter-task communication links
- Infer backward from kernel API call sites:
-
Decompile split by task:
- Independent analysis per-task entry: the task creation API's entry parameter → function → renamed to task_<priority>_<name>, marking entries task by task in Ghidra
- Blocking points within a task (delay/wait semaphore/receive queue/wait event) are scheduling switch points; use the blocking points to split the task logic into state segments for analysis
- Inter-task communication objects (queues/semaphores) connect different tasks: first draw a "task-object-task" relationship diagram, then dig into each one in turn according to the diagram
Platform branches (references)
The "driver/service models" of the following four platforms differ enough from the general task table method to warrant separate sections——First read the branch to determine the runtime model, then return to this main document for task and object analysis:
- [[qnx]] —— User-space server model:resource manager(
dispatch_create→resmgr_attach→dispatch_block/handler, the underlying layer is theMsgReceive)、connect/I/O two function tables and theiofunc_*distinction of the default implementation,devctland_IO_DEVCTL、io-pkt anddevnp-*.so(including single-threaded stack context), message-drivenpriority inheritanceand server boost,MsgSendsynchronous blocking vs pulse - [[vxworks]] —— DKM(
.out, kernel mode) vs RTP (.vxe, user mode), the linking relationship between DKM andglobal symbol environment,undefined symbolthe real cause of (VIP does not include the component), unresolved in independent modules is normal, VSB/VIP and "same version different ABI environment" - [[zephyr]] —— Compile-time device graph:
DEVICE_DEFINE/DEVICE_DT_DEFINEand linker section,SYS_INITlevel/prio andreturn value consequences、iterable sections(a regularly arranged struct is not necessarily an obfuscation table),/chosenand/aliasesare not hardware nodes, the map file decides attribution,LLEXT(export symbol table,llext_unloadpointer invalidated afterward, under User Mode requiresllext_add_domain) - [[rtems]] —— Single address space + build-time configuration:
confdefs.handCONFIGURE_INITuniqueness,rtems_driver_address_tablesix entries and major = table index、rtems_io_register_name、The runtime linker is not the Unix shared library model(libfoo.a:bar.o, lazy/immediate binding equivalence, unresolved does not report an error, duplicate names are an error, the base image requiresrtems-syms) - [[partitioned-rtos]] —— Partitioned systems (INTEGRITY / ARINC 653 / VxWorks 653 / PikeOS):major frame + partition window two-level scheduling (PikeOS three-level); partition modes IDLE/COLD_START/WARM_START/NORMAL (a process "not running" is often because it has not yet entered NORMAL);sampling port vs queuing portoverwrite semantics; INTEGRITY's fixed CPU/memory budget and static startup table; PikeOS's APEX fixed-address service tableand the POSIX guest's system thread mapping
- [[nuttx]] —— FLAT / PROTECTED / KERNEL three builds: the same API produces completely different artifacts; PROTECTED'sautomatically generated syscall proxy(an extremely short function + immediate trap, not a hook);dual heap(user heap / kernel heap); the driver's upper half / lower half layering
- [[freertos-context]] —— ISR and task context: task version vs
...FromISR()version API,pxHigherPriorityTaskWokenandportYIELD_FROM_ISR(the real switch happens after the interrupt exits),configMAX_SYSCALL_INTERRUPT_PRIORITY(a more urgent ISR cannot call a single API, and the priority value direction is reversed);FreeRTOS-MPU restricted tasks and runtime zone change - [[threadx-modules]] —— module preamble(must be at the first address, properties bits determine privilege/MPU) + the request ID invokes the resident Module Manager via software dispatch(essentially a syscall table, not obfuscation) + XIP and copy loading
- [[ecos]] —— Runtime: three layers ISR / DSR / thread, three synchronization levels and their respective locks (ISR cannot use DSR-level locks; DSR can signal condition variables but cannot wait), three driver models;Build time: the CDL configuration system determines what is compiled in and the build target, the HAL vector table (VSR) and virtual vectors, the RedBoot boot sequence andconstructors run after the vector table is initialized(region overlap will be overwritten), the fate of debug stubs
- [[ucos-sysbios]] —— Interrupt entry and exit participate in scheduling: µC/OS's
OSIntEnter/OSIntExitpaired protocol and "only when the last nested ISR exits is a switch judged"; SYS/BIOS's Hwi > Swi > Task layering,Swi_disable()also disables Task scheduling, Hwi/Swi share the ISR stack - [[safertos-rtx5]] —— Isolation features of certified RTOSes: SAFERTOS ESM's API access policy / object access policy / indirect object ID / per-task region; RTX5's Safety Class / MPU protection domain / thread watchdog / object and SVC pointer checks、ISR FIFO deferred queue(on overflow the system state is already inconsistent)
- [[tkernel-toppers]] —— Context validity: T-Kernel's three contexts of task / quasi-task / task-independent and device drivers (quasi-task part, must be reentrant, mutual exclusion not guaranteed);TOPPERS's four families(ASP3 / HRP3 / FMP3 / HRMP3) and static configuration generation
- [[ose-oseck]] —— Signal model(not POSIX signals;signal numbers are not unique, so a secondary ID is needed; register before sending or it is lost; one event may be associated with multiple signals that must all be drained) + process/block/segment/pool memory ownership(can only allocate inone's own pool, cross-domain sends are copied) + supervision and error handlers (including "what the end marker can detect and what it cannot") + differences between OSE and OSEck andheterogeneous boundaries(signals cross core/CPU/DSP/node; the system information interface can view the signal space and saved registers/stack)
- [[nucleus]] —— Linear memory mapping + protected regions + entitlement(MMU on Cortex-A, MPU on Cortex-M;not a separate VA per process);
NU_PARTITION_POOLis a fixed-block memory pool(a terminology trap, homonymous with isolation domain);NU_SUSPENDunder this the task is already suspended when the pool is empty;modules can be dynamically reloaded/restarted/updated without downtime
Cross-domain federation
- [[re-fw-extract]]: image extraction and format identification prerequisites (binwalk unpacking, magic scanning, endianness determination, offset splitting)
- [[re-fw-emulate]]: emulate the target firmware when dynamic verification is needed (running tasks, triggering scheduling, observing switch timing)
- [[re-automotive]]: QNX automotive head-unit infotainment scenario linkage (automotive system-side firmware/process analysis, complementary to the CAN/ECU side)
- [[re-binary-core]]: deep decompilation foundation for RTOS applications (define structs with [[re-ghidra]], or choose [[re-radare2]] based on environment)
- Discover malicious logic → [[re-malware]]; task and server communication → [[re-protocol]]
- This skill can be invoked by the MCU firmware deep-dive path of the [[re-firmware]] gateway
Common Pitfalls and Traps
- Treating code before scheduler startup as a task for analysis: Symptom — treating the initialization sequence between the reset handler and scheduler startup as the main logic of some task, analyzing for a long time without matching the business logic; Cause — startup code (clock/peripherals/memory/task creation) executes before the scheduler and does not belong to any task, and its call form resembles a task entry; Countermeasure — first locate the scheduler startup call site (vTaskStartScheduler/osKernelStart/tx_kernel_enter/rt_system_scheduler_start/kernelInit), classify everything before it as initialization, and always take task entries from the entry parameter of the creation API
- stripped firmware has no task names: Symptom — name fields in the TCB are all empty/garbled/all 0; Cause — the firmware is stripped of symbols and its string table is trimmed, leaving nothing readable for pcTaskName/tx_thread_name; Countermeasure — distinguish tasks by stack pointer range (each task has its own stack interval), priority value, and creation order number; treat task names only as auxiliary clues
- Linked list vs static table structure differences: Symptom — assuming a "static array task table" makes the task list impossible to find in the firmware; Cause — each kernel organizes things differently: FreeRTOS uses a static linked-list array (pxReadyTasksLists indexed by priority) + current pointer pxCurrentTCB, ThreadX uses the global doubly linked list _tx_thread_created_list, Zephyr uses an slist of all threads + ready_q bitmap queue, RT-Thread uses a priority bitmap + array linked list, VxWorks hangs tasks on a global task linked list (taskIdListGet can enumerate semantics), QNX/INTEGRITY layouts are not public and must be inferred backward from creation/service call sites and handle tables; Countermeasure — identify the kernel first, then choose the corresponding structure (step 3); do not apply one kernel's layout to another
- Newer RTOS cannot be found by signatures after strings/symbols are removed: Symptom — strings and the symbol table contain nothing, and kernel identification gets stuck; Cause — release firmware is often stripped of symbols, has assertions/strings disabled, and even trims version strings; Countermeasure — fall back to structural features: ThreadX magic IDs (THRD/QUEU/SEMA…), FreeRTOS critical section patterns (fixed sequence of BASEPRI save/restore on Cortex-M), scheduler entry and context switch code shape, and compare TCB field offsets by version (infer the full structure from how task entry parameters are written into the TCB)
- Treating idle/low-priority fallback tasks as business tasks: Symptom — deeply analyzing a spin-loop task as if it were the main business logic; Cause — FreeRTOS automatically creates prvIdleTask (lowest priority 0), Zephyr has an idle thread, VxWorks has a tIdle task; they only run hook/cleanup/low-power code and are unrelated to business logic; Countermeasure — sort the task list by priority, first exclude the lowest-priority, non-blocking infinite loop as a fallback task, and look for business tasks among high-priority tasks and tasks containing blocking points (queue/semaphore waits)
- Treating context switch/scheduler code as ordinary business functions: Symptom—the PendSV/SVC handler or the scheduler loop is tracked as business logic, and the call relationships are chaotic; Cause—the RTOS's context switch (saving/restoring the register set, switching pxCurrentTCB/current) and the task code both belong to the firmware, so the decompiler treats them as ordinary functions; Countermeasure—after identifying the switch characteristics (saving the full register set, updating the current task pointer, restoring PC/LR from the top of the stack), mark them as scheduling code and do not go deeper; for task logic, only look at the call tree below the creation API entry point
- The structure of commercial RTOSes has no public definition, and offsets from old materials cannot be applied: Symptom—using the WIND_TCB offsets from VxWorks 5.x found online to parse VxWorks 7 firmware yields garbled data; Cause—starting with VxWorks 7, WIND_TCB is an opaque type (taskLib.h only provides VX_WIND_TCB_SIZE), the QNX thread control block and INTEGRITY process table layouts are not public, and they change with version/SMP configuration; Countermeasure—treat field names only as semantic clues, always infer offsets from the context switch/creation code on site, and verify them using field read/write patterns
- The task name field is a string pointer, not an inline array: Symptom—looking in the TCB for a fixed-length name array as is customary in FreeRTOS, but what is read is an address value; Cause—VxWorks td_name is a char* pointing to a string pool, and QNX/INTEGRITY thread names are likewise; Countermeasure—after obtaining the field, first determine whether it is an address or inline data (use cross-references to see whether it points to a string area); after the firmware strips strings, the name field becomes invalid, so distinguish tasks by stack range/priority instead
- QNX firmware is an IFS image and cannot be searched directly as a raw binary: Symptom—procnto or process code cannot be found in the whole firmware block; Cause—files inside a QNX image are often LZO-compressed, so neither strings nor code are visible; Countermeasure—first locate the image by the startup header (0x00ff7eeb) + "imagefs" signature, decompress and extract it with dumpifs (add -z when the startup header fields have been modified), and obtain each process ELF before analyzing
- INTEGRITY has no dynamic creation at runtime: Symptom—analyzing partition mode switching (SET_PARTITION_MODE's COLD_START/WARM_START/NORMAL) or process start/stop (START/STOP) as "task creation" logic; Cause—INTEGRITY resources are statically allocated at startup, and at runtime there is mainly state transition with no dynamic memory allocation; Countermeasure—creation-type code only appears in the initialization/configuration path; business logic is organized according to periodic scheduling (major frame) and APEX service calls