命令字:!ndx.devtree

用法描述

该命令可展示设备树的层级结构,呈现设备树节点的关键信息(含节点地址、名称、标志位及深度),并支持查看节点的属性数据(含属性名称、地址、值指针及长度)。
命令行参数支持:
-v 显示每个节点的属性信息
-v1 仅显示指定节点的属性
-a <地址> 指定自定义起始节点地址
-h /–help/ ? 显示帮助信息

开发过程

1. 找到关键全局变量

关键全局变量 作用
of_root Linux 内核中设备树的 根节点指针(struct device_node *of_root),是设备树遍历的起点

在drivers\of\base.c文件中
of_kset = kset_create_and_add(“devicetree”, NULL, firmware_kobj);
作用:在 sysfs 文件系统中创建并注册一个名为devicetree的kset(内核对象集合)。

for_each_of_allnodes(np) { _ofattach_node_sysfs(np); … }
作用:遍历所有设备树节点,并将每个节点挂载到 sysfs 中。

proc_symlink(“device-tree”, NULL, “/sys/firmware/devicetree/base”);
作用:在/proc文件系统中创建一个符号链接,指向 sysfs 中的设备树根节点。

_offind_all_nodes是具体的遍历树函数(dfs),当prev为NULL(首次调用遍历),返回设备树的根节点of_root,作为遍历的起点。

每个设备节点存放了属性链表的头节点, 获取头节点遍历链表获取的属性各个字段

2. 相关数据结构

设备树节点的结构体

3. 读取变量和相关偏移,输出信息

#include "ndx.h"
#include <windows.h>
#include <minwindef.h>
#include <WDBGEXTS.H>
#include <cstdio>
#include <vector>
#include <string>
#include <string.h>
#include <cctype>
#include <cstdlib>
#include <cerrno>
#include <wdbgexts.h>
#include <fstream>
#include <iostream>
#include "devtree.h"

// 全局变量,存储device_node结构体字段偏移量
static device_node_offsets g_device_node_offsets = { 0 };
// 全局变量,存储property结构体字段偏移量
static property_offsets g_property_offsets = { 0 };

// 初始化device_node和property结构体字段偏移量
bool InitOffsets(device_node_offsets* node_offsets, property_offsets* prop_offsets) {
    bool success = true;

    // 初始化device_node结构体字段偏移量
    if (node_offsets) {
        if (GetFieldOffset("device_node", "name", &node_offsets->name_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "phandle", &node_offsets->phandle_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "full_name", &node_offsets->full_name_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "fwnode", &node_offsets->fwnode_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "properties", &node_offsets->properties_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "deadprops", &node_offsets->deadprops_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "parent", &node_offsets->parent_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "child", &node_offsets->child_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "sibling", &node_offsets->sibling_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("device_node", "_flags", &node_offsets->_flags_offset) != 0) {
            success = false;
        }

        GetFieldOffset("device_node", "data", &node_offsets->data_offset);
        GetFieldOffset("device_node", "unique_id", &node_offsets->unique_id_offset);
        GetFieldOffset("device_node", "irq_trans", &node_offsets->irq_trans_offset);
    }

    // 初始化property结构体字段偏移量
    if (prop_offsets) {
        if (GetFieldOffset("property", "name", &prop_offsets->name_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("property", "length", &prop_offsets->length_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("property", "value", &prop_offsets->value_offset) != 0) {
            success = false;
        }
        if (GetFieldOffset("property", "next", &prop_offsets->next_offset) != 0) {
            success = false;
        }
    }

    return success;
}

// phandle哈希函数实现
uint32_t of_phandle_cache_hash(phandle handle) {
    return handle & (OF_PHANDLE_CACHE_SZ - 1);
}




// 显示节点的属性信息
void show_node_properties(ULONG64 node_addr) {
    // 确保属性偏移量已初始化
    if (g_property_offsets.name_offset == 0) {
        if (!InitOffsets(NULL, &g_property_offsets)) {
            dprintf("ERROR: Failed to initialize property offsets\n");
            return;
        }
    }

    ULONG bytes_read = 0;
    ULONG64 properties_ptr = 0;

    // 读取properties指针
    if (!ExtReadMemory(node_addr + g_device_node_offsets.properties_offset, &properties_ptr, sizeof(ULONG64), &bytes_read) || bytes_read != sizeof(ULONG64)) {
        dprintf("ERROR: Cannot read node properties pointer at address %p (offset +0x%x)\n",
            (void*)node_addr, g_device_node_offsets.properties_offset);
        return;
    }

    // 遍历属性链表
    ULONG64 prop_addr = properties_ptr;
    int prop_count = 0;

    while (prop_addr) {
        ULONG64 name_ptr = 0;
        ULONG64 value_ptr = 0;
        ULONG length = 0;
        ULONG64 next_prop = 0;

        // 读取属性名称指针
        if (!ExtReadMemory(prop_addr + g_property_offsets.name_offset, &name_ptr, sizeof(ULONG64), &bytes_read)) {
            break;
        }

        // 读取属性值指针
        if (!ExtReadMemory(prop_addr + g_property_offsets.value_offset, &value_ptr, sizeof(ULONG64), &bytes_read)) {
            break;
        }

        // 读取属性长度
        if (!ExtReadMemory(prop_addr + g_property_offsets.length_offset, &length, sizeof(ULONG), &bytes_read)) {
            break;
        }

        // 读取下一个属性指针
        if (!ExtReadMemory(prop_addr + g_property_offsets.next_offset, &next_prop, sizeof(ULONG64), &bytes_read)) {
            break;
        }

        // 读取属性名称
        char name[128] = { 0 };
        if (name_ptr) {
            ExtReadMemory(name_ptr, name, sizeof(name) - 1, &bytes_read);
        }

        // 显示属性信息 - 使用固定宽度格式化输出,使各列对齐
        dprintf("  Property[%03d]: %-30s  Address: %-16p  Value: %-16p  Length: %-8u\n",
            prop_count, name, (void*)prop_addr, (void*)value_ptr, length);

        // 移动到下一个属性
        prop_addr = next_prop;
        prop_count++;
    }

    if (prop_count == 0) {
        dprintf("  No properties found\n");
    }
}

// 递归显示设备树结构,带有层级关系
// 显示节点信息的函数
void show_node_info(ULONG64 node_addr, int depth) {
    if (g_device_node_offsets.name_offset == 0) {
        if (!InitOffsets(&g_device_node_offsets, NULL)) {
            dprintf("ERROR: Failed to initialize device_node offsets\n");
            return;
        }
    }

    if (node_addr == 0) {
        dprintf("ERROR: Invalid node address (0x%p) at depth %d\n", (void*)node_addr, depth);
        return;
    }

    ULONG bytes_read = 0;
    ULONG64 fullname_ptr = 0;

    // 读取fullname指针
    if (!ExtReadMemory(node_addr + g_device_node_offsets.full_name_offset, &fullname_ptr, sizeof(ULONG64), &bytes_read) || bytes_read != sizeof(ULONG64)) {
        dprintf("ERROR: Cannot read node fullname pointer at address %p (offset +0x%x)\n",
            (void*)node_addr, g_device_node_offsets.full_name_offset);
        return;
    }

    // 读取fullname字符串
    char fullname[256] = { 0 };
    if (fullname_ptr) {
        ULONG bytes_read = 0;
        if (!ExtReadMemory(fullname_ptr, fullname, sizeof(fullname) - 1, &bytes_read)) {
            dprintf("ERROR: Cannot read fullname string at address %p\n", (void*)fullname_ptr);
        }
    }

    // 读取节点的_flags和data字段
    ULONG flags = 0;
    if (g_device_node_offsets._flags_offset != 0) {
        if (!ExtReadMemory(node_addr + g_device_node_offsets._flags_offset, &flags, sizeof(ULONG), &bytes_read)) {
            dprintf("ERROR: Cannot read _flags at address %p \n", node_addr + g_device_node_offsets._flags_offset);
        };
    }

    // 计算缩进空格数
    int indent = depth * 2;

    // 移除空行,直接输出节点信息
    if (fullname[0] != '\0') {
        // 计算需要补充的空格数,确保地址对齐
        int name_length = indent + (int)strlen(fullname);
        int padding = (name_length < 50) ? (50 - name_length) : 2;

        // 在一行内输出所有节点信息
        dprintf("%*s%-s%*s%p   Flags: 0x%08x  Depth: %d\n",
            indent, "", fullname,
            padding, "",
            (void*)node_addr,
            flags,
            depth);
    }
}

void show_device_tree_recursive(ULONG64 node_addr, int depth, bool traverse_siblings, bool show_properties) {
    if (node_addr == 0) {
        return;
    }

    ULONG bytes_read = 0;
    ULONG64 child = 0, sibling = 0;

    // 显示当前节点信息
    show_node_info(node_addr, depth);

    // 如果需要显示属性信息,则调用show_node_properties函数
    if (show_properties) {
        show_node_properties(node_addr);
    }

    // 读取子节点指针
    if (!ExtReadMemory(node_addr + g_device_node_offsets.child_offset, &child, sizeof(ULONG64), &bytes_read)) {
        dprintf("ERROR: Cannot read child pointer at address %p (offset +0x%x)\n",
            (void*)node_addr, g_device_node_offsets.child_offset);
        return;
    }

    // 递归处理子节点
    if (child) {
        show_device_tree_recursive(child, depth + 1, true, show_properties); // 子节点总是遍历其兄弟节点
    }

    // 读取兄弟节点指针
    if (!ExtReadMemory(node_addr + g_device_node_offsets.sibling_offset, &sibling, sizeof(ULONG64), &bytes_read)) {
        dprintf("ERROR: Cannot read sibling pointer ");
        return;
    }

    // 处理兄弟节点
    if (sibling && traverse_siblings) {
        show_device_tree_recursive(sibling, depth, traverse_siblings, show_properties);
    }
}

// 显示phandle缓存
void show_phandle_cache() {
    dprintf("Phandle Cache (128 entries):\n");
    dprintf("please wait...\n");

    // 确保偏移量已初始化
    if (g_device_node_offsets.name_offset == 0 || g_device_node_offsets.phandle_offset == 0) {
        if (!InitOffsets(&g_device_node_offsets, NULL)) {
            dprintf("ERROR: Failed to initialize device_node offsets\n");
            return;
        }
    }

    // 获取phandle_cache符号地址
    ULONG64 phandle_cache_addr = 0;
    GetExpressionEx("lk!phandle_cache", &phandle_cache_addr, NULL);

    if (phandle_cache_addr == 0) {
        dprintf("Error: Cannot find phandle_cache symbol\n");
        return;
    }

    dprintf("phandle_cache address: %p\n", phandle_cache_addr);

    // 读取128个device_node指针
    ULONG64 cache_entries[OF_PHANDLE_CACHE_SZ];
    ULONG bytes_read = 0;

    if (!ExtReadMemory(phandle_cache_addr, cache_entries, sizeof(cache_entries), &bytes_read)) {
        dprintf("Error: Cannot read phandle_cache from memory\n");
        return;
    }

    // 读取所有非空节点的phandle和name
    int valid_entries = 0;
    ULONG64 name_ptrs[OF_PHANDLE_CACHE_SZ];
    char names[OF_PHANDLE_CACHE_SZ][256];
    bool read_success[OF_PHANDLE_CACHE_SZ] = { false };

    // 先找出所有非空的节点地址
    std::vector<int> valid_indices;
    for (int i = 0; i < OF_PHANDLE_CACHE_SZ; i++) {
        if (cache_entries[i] != 0) {
            valid_indices.push_back(i);
        }
    }

    // 一次性读取所有节点的phandle和name指针
    for (int idx : valid_indices) {


        // 读取name指针
        if (read_success[idx]) {
            read_success[idx] = ExtReadMemory(cache_entries[idx] + g_device_node_offsets.name_offset,
                &name_ptrs[idx], sizeof(ULONG64), &bytes_read);

            // 读取name字符串
            if (read_success[idx] && name_ptrs[idx]) {
                ExtReadMemory(name_ptrs[idx], names[idx], 255, &bytes_read);
            }
        }
    }
    // 显示结果
    for (int idx : valid_indices) {
        if (read_success[idx]) {
            dprintf("%5d  %12p  %s\n", idx, (void*)cache_entries[idx],
                name_ptrs[idx] && names[idx][0] ? names[idx] : "<no name>");
            valid_entries++;
        }
        else {
            dprintf("%5d  %12p  <read error>\n", idx, (void*)cache_entries[idx]);
        }
    }

    dprintf("\nTotal valid entries: %d/%d\n", valid_entries, OF_PHANDLE_CACHE_SZ);
}

/**
 * 显示使用帮助
 */
static void show_usage() {
    dprintf(
        "Linux Kernel Device Tree Commands:\n"
        "  !ndx.devtree                       Show device tree (default)\n"
        "  !ndx.devtree -v                    Show property information for each node\n"
        "  !ndx.devtree -v1                   Show only properties of specified node\n"
        "  !ndx.devtree -a <address>          Specify custom start node address\n"
        "  !ndx.devtree -a <address> -p       Show device tree from custom address with properties\n"
        "  !ndx.devtree -a <address> -p1      Show only properties of node at custom address\n"
        "  !ndx.devtree -h, --help, ?         Show this help\n"
    );
}

// 解析设备树命令参数
bool parse_devtree_command(const char* args, ULONG64* out_node_addr, bool* is_custom_address, bool* show_properties, bool* show_single_node_properties) {
    if (!out_node_addr || !is_custom_address || !show_properties || !show_single_node_properties) {
        return false;
    }

    *out_node_addr = 0;
    *is_custom_address = false;
    *show_properties = false;
    *show_single_node_properties = false;

    // 创建参数副本以避免修改原始字符串
    if (args && *args) {
        char args_copy[256];
        strncpy_s(args_copy, sizeof(args_copy), args, _TRUNCATE);

        // 使用空格分割参数
        char* context = nullptr;
        char* token = strtok_s(args_copy, " ", &context);

        while (token) {
            // 解析命令选项
            if (_stricmp(token, "-a") == 0) {
                // 按地址指定根节点
                char* addr_str = strtok_s(nullptr, " ", &context);
                if (addr_str) {
                    if (GetExpressionEx(addr_str, out_node_addr, NULL)) {
                        *is_custom_address = true;
                        dprintf("Using custom start node address: %p\n", (void*)*out_node_addr);
                    }
                    else {
                        dprintf("ERROR: Invalid address format '%s'\n", addr_str);
                        return false;
                    }
                }
                else {
                    dprintf("ERROR: Missing address after -a parameter\n");
                    return false;
                }
            }
            else if (_stricmp(token, "-v") == 0) {
                // 显示属性信息
                *show_properties = true;
                dprintf("Property information will be displayed\n");
            }
            else if (_stricmp(token, "-v1") == 0) {
                // 只显示指定节点的属性信息
                *show_single_node_properties = true;
                dprintf("Only properties of the specified node will be displayed\n");
            }
            else if (_stricmp(token, "-h") == 0 || _stricmp(token, "--help") == 0 || _stricmp(token, "?") == 0) {
                // 显示帮助
                show_usage();
                return false;
            }
            else {
                dprintf("ERROR: Unknown option '%s'\n", token);
                show_usage();
                return false;
            }

            // 获取下一个参数
            token = strtok_s(nullptr, " ", &context);
        }
    }

    // 如果没有指定自定义地址,则使用根节点
    if (!*is_custom_address) {
        // 获取根节点
        ULONG64 of_root_addr = 0;
        GetExpressionEx("lk!of_root", &of_root_addr, NULL);

        if (of_root_addr) {
            ULONG bytes_read = 0;
            if (!ExtReadMemory(of_root_addr, out_node_addr, sizeof(ULONG64), &bytes_read) || !*out_node_addr) {
                dprintf("Error: Cannot read of_root address from memory\n");
                return false;
            }
            dprintf("Root Node Address: %p\n", (void*)*out_node_addr);
        }
        else {
            dprintf("Error: Cannot find of_root symbol address\n");
            return false;
        }
    }

    return true;
}

DECLARE_API(devtree) {
    //show_phandle_cache();

    // 确保偏移量已初始化
    if (g_device_node_offsets.name_offset == 0) {
        if (!InitOffsets(&g_device_node_offsets, NULL)) {
            dprintf("ERROR: Failed to initialize device_node offsets\n");
            return;
        }
    }

    // 解析命令参数
    ULONG64 start_node_addr = 0;
    bool is_custom_address = false;
    bool show_properties = false;
    bool show_single_node_properties = false;
    if (!parse_devtree_command(args, &start_node_addr, &is_custom_address, &show_properties, &show_single_node_properties)) {
        return;
    }

    // 如果指定了只显示单个节点的属性
    if (show_single_node_properties) {
        // 先显示节点信息,但不遍历子节点或兄弟节点
        show_node_info(start_node_addr, 0);

        // 再显示属性信息
        show_node_properties(start_node_addr);
    }
    else {
        // 显示设备树
        // 如果是自定义地址,根节点不遍历它的兄弟节点
        show_device_tree_recursive(start_node_addr, 0, !is_custom_address, show_properties);
    }
}

4. 实例测试

!ndx.devtree

ls /proc/device-tree/clocks

作者:郭建程  创建时间:2025-09-19 10:50
最后编辑:郭建程  更新时间:2025-09-22 20:17