NanoCode实现扩展命令:
在NanoCode/exts目录下有一个文件:ndx.dll,它用于NanoCode的插件扩展。
后续也是更新这个文件来实现扩展命令。
从github上下载对应的ndx文件到本地,打开ndx.sln进入到visual stadio。
之后可以看到文件ndx.def
这是DLL模块定义文件(.def),用于声明 DLL(如 ndx.cmdline)的导出函数
先测试一下能不能编译通过,检查环境。
例子演示:
一、创建文件
下面我会用一个例子演示怎么写扩展命令,以及在NanoCode上使用这个命令:
1、在ndx.def中添加你需要扩展的命令,后续在NanoCode可以执行这个命令。
2、创建.cpp文件(如cmdline.cpp)
3、引入相关头文件,使用DECLARE_API创建一个用于处理 “cmdline” 相关功能的接口实现。
注意:DECLARE_API()内的名称要和在ndx.def中的一致
二、明确目标,分析内核源码:
比如我现在要显示proc/cmdline文件中的内容,查找内核源码。
在fs\proc\cmdline.c有内核相关实现:
// SPDX-License-Identifier: GPL-2.0
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
// 读取/proc/cmdline时的回调函数,输出内核启动命令行
static int cmdline_proc_show(struct seq_file *m, void *v)
{
seq_puts(m, saved_command_line); // 写入保存的启动命令行
seq_putc(m, '\n'); // 添加换行符
return 0;
}
// 初始化函数,创建/proc/cmdline文件
static int __init proc_cmdline_init(void)
{
// 创建proc文件"cmdline",关联显示回调
proc_create_single("cmdline", 0, NULL, cmdline_proc_show);
return 0;
}
// 注册为文件系统初始化阶段执行的函数
fs_initcall(proc_cmdline_init);
proc_cmdline_init函数创建了文件”cmdline” ,读取文件是调用cmdline_proc_show显示相关信息。
既然我们要显示文件中的内容,那就重点看cmdline_proc_show这个函数,了解内容存放在什么样的数据结构。
cmdline_proc_show中调用了seq_puts(m, saved_command_line)
传入两个参数:struct seq_file *m,char *saved_command_line。
void seq_puts(struct seq_file *m, const char *s)
{
int len = strlen(s);
if (m->count + len >= m->size) {
seq_set_overflow(m);
return;
}
memcpy(m->buf + m->count, s, len);
m->count += len;
}
EXPORT_SYMBOL(seq_puts);
struct seq_file 是 Linux 内核中用于处理序列数据输出的核心数据结构,主要用于简化内核中类似 /proc 文件系统的文本信息生成逻辑
saved_command_line是字符指针,指向字符串数组。
seq_puts(m, saved_command_line); 的作用是将内核启动时的命令行参数(saved_command_line)写入到 seq_file 的缓冲区中,以便通过 /proc 或 /sys 文件系统向用户空间输出这些信息。
三、实现代码
现在我们清楚,需要读取saved_command_line这个全局变量来实现cmdline的扩展命令。
1、调用相关API实现内存读取并且打印
#include "ndx.h"
#include <windows.h>
#include <minwindef.h>
#include <WDBGEXTS.H>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cerrno>
#include <fstream>
#include <iostream>
DECLARE_API(cmdline)
{
ULONG64 saved_command_line = 0;
ULONG read;
GetExpressionEx("lk!saved_command_line", &saved_command_line, NULL); //获取指定符号地址
if (saved_command_line == 0) {
dprintf("Failed to get saved_command_line address. Please fix symbol.\n");
return;
}
ULONG64 linux_cmdline = 0;
char cmdline[1024];
ReadPointer(saved_command_line, &linux_cmdline); //读取指针地址
dprintf("lk!saved_command_line: %p -> %p\n", saved_command_line, linux_cmdline);
ExtReadMemory(linux_cmdline, cmdline, sizeof(cmdline), &read); //读取指针指向的字符穿数组
dprintf("Read %d bytes.\nContent: %s", read, cmdline); //打印字符串数组
}
2、编译生成ndx.dll文件
用everything搜索ndx.dll快速查找文件(也可以去对应目录查找),找到编译生成的文件。
3、替换NanoCode\exts目录下的ndx.dll文件:
四、测试结果,代码调试
1、打开Nanode软件,进入内核调试,如图选择好参数
2、点击中断(每次进入内核调试,会自动中断)。
如果中断按键为灰色,无法中断,重启软件多试几次。
或者插拔一下USB接口再试。
3、中断后输入命令:!ndx.cmdline (这里的cmdline就是在ndx.def注册的命令,执行其它命令可以替换);
结果显示:
4、验证:linux下输入 cat /proc/cmdline 查看文件内容,和Nancode输出的对比,如果一致说明成功。
在一些特殊情况下,对比发现不一致也不一定是错误的,一些文件是否是动态读的,会存在不同。
具体得看内核源码中的细节。
以上是cmdline扩展命令的全部演示,实现其它命令大致按上述的步骤完成。
常用的API函数:
GetExpressionEx(“module!symbol_name”, &symbol_addr, NULL)
- 作用 :获取指定符号的内存地址
- 参数 :符号名称(模块!符号格式)、地址输出变量、可选参数
- 返回 :成功返回非零值,失败返回零
GetFieldOffset(“struct_name”, “field_name”, &field_offset)
- 作用 :获取结构体字段的偏移量
- 参数 :结构体名称、字段名称、偏移量输出变量
- 返回 :成功返回0,失败返回非零值
GetTypeSize(“type_name”)
- 作用 :获取指定类型的大小
- 参数 :类型名称
- 返回 :类型大小(字节),失败返回0
ExtReadMemory(target_addr, &value, sizeof(value), NULL)
- 作用 :从目标进程内存读取数据
- 参数 :目标地址、输出缓冲区、读取大小、可选参数
- 返回 :成功返回非零值,失败返回零
ExtWriteMemory(flags_addr, &new_flags, sizeof(new_flags), NULL)
- 作用 :向目标进程内存写入数据
- 参数 :目标地址、输入缓冲区、写入大小、可选参数
- 返回 :成功返回非零值,失败返回零
dprintf(“format_string\n”, args…)
- 作用 :在调试器中输出格式化信息
- 参数 :格式字符串和参数(类似printf)
- 返回 :无返回值
DECLARE_API(command_name) { /* 实现 */ }
- 作用 :声明调试器扩展命令
- 参数 :命令名称
- 用法 :在NanoCode中使用 !ndx.command_name
命令行参数解析核心函数介绍
1、args 参数
在DECLARE_API 函数中, args 是一个 const char* 类型的参数,包含用户在调试器中输入的完整命令行字符串。
DECLARE_API(mycommand) {
INIT_API();
// args 包含用户输入的所有参数,例如:
// 用户输入:!mycommand -p -m kernel -f function_name
// args 内容:"-p -m kernel -f function_name"
}
2、核心字符串处理函数
strstr() - 子字符串查找
功能 :在字符串中查找子字符串的第一次出现位置
用法示例 :
// 检查是否包含 -p 参数
if (strstr(args, "-p") != NULL) {
dprintf("找到 -p 参数\n");
}
// 查找参数位置
const char* pos = strstr(args, "-m");
if (pos) {
dprintf("-m 参数位置:%d\n", pos -
args);
}
strtok() - 字符串分割
功能 :将字符串按指定分隔符分割成多个部分
用法示例 :
// 必须先复制字符串,因为 strtok 会修改原字符串
char args_copy[512];
strncpy_s(args_copy, sizeof(args_copy),
args, _TRUNCATE);
// 按空格分割
char* token = strtok(args_copy, " ");
while (token != NULL) {
dprintf("参数: %s\n", token);
token = strtok(NULL, " "); // 注意这里
传入 NULL
}
strlen() - 字符串长度
功能 :计算字符串长度(不包括结尾的 \0 )
用法示例 :
if (args && strlen(args) > 0) {
dprintf("参数长度: %d\n", strlen
(args));
}
strncmp() - 字符串比较
功能 :比较两个字符串的前 n 个字符
返回值 :
- 0: 相等
- <0: str1 < str2
- 0: str1 > str2
用法示例 :// 检查参数是否以 -m 开头 if (strncmp(token, "-m", 2) == 0) { if (strlen(token) > 2) { // -mvalue 格式 const char* value = token + 2; } else { // -m value 格式,需要获取下一个 token } }
最后编辑:郭建程 更新时间:2025-08-29 18:57