在日常工作中常有直接操作寄存器或者某一物理地址的需求,busybox中提供了devmem。通过它可以读写物理内存。它的实现借助mmap和/dev/mem,通过mmap将/dev/mem物理地址映射到用户空间,devmem就可以像操作虚拟地址一样进行读写。
hexdump同样也可以类似devmem的功能。
如果需要在用户空间获取内核某个变量值,可以使用devkmem通过/dev/kmem进行。
下面分别介绍这三种工具。
1. devmem操作物理地址,它是如何做到的?
用户空间是无法直接操作物理地址的;但是日常工作中常需要对某一物理地址进行读写,尤其是寄存器。
devmem可以实现这个功能。那么devmem做了什么?/dev/mem在内核中优势如何实现的呢?
1.1 devmem工具使用
devmem使用介绍如下:
BusyBox v1.27.2 (2019-04-16 17:00:28 CST) multi-call binary.
Usage: devmem ADDRESS [WIDTH [VALUE]]
Read/write from physical address
ADDRESS Address to act upon
WIDTH Width (8/16/...)
VALUE Data to be written
devmem的能力有限,只能处理最大64字节的数目。
下面向0xfc20700这个地址写入32位数据0x12345678:
devmem 0xfc20700 32 0x12345678
然后从0xfc20700读取进行验证。
devmem 0xfc20700 32
0x12345678
1.2 devmem工具分析
从下面的代码可知,devmem解析参数,然后将地址转换成页面对齐的地址。mmap将/dev/mem的输入地址偏移的页面映射到用户空间,然后读取数值。
int devmem_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int devmem_main(int argc UNUSED_PARAM, char **argv)
{
void *map_base, *virt_addr;
uint64_t read_result;
uint64_t writeval = writeval; /* for compiler */
off_t target;
unsigned page_size, mapped_size, offset_in_page;
int fd;
unsigned width = 8 * sizeof(int);
/* ADDRESS */
if (!argv[1])
bb_show_usage();
errno = 0;
target = bb_strtoull(argv[1], NULL, 0); /* allows hex, oct etc */---------------第一个参数是地址
/* WIDTH */
if (argv[2]) {------------------------------------------------------------------第二个参数,在写的情况下,需要知道写数据的位宽。
if (isdigit(argv[2][0]) || argv[2][1])
width = xatou(argv[2]);
else {
static const char bhwl[] ALIGN1 = "bhwl";
static const uint8_t sizes[] ALIGN1 = {
8 * sizeof(char),
8 * sizeof(short),
8 * sizeof(int),
8 * sizeof(long),
0 /* bad */
};
width = strchrnul(bhwl, (argv[2][0] | 0x20)) - bhwl;
width = sizes[width];
}
/* VALUE */
if (argv[3])-----------------------------------------------------------------第三个参数,待写入数值。
writeval = bb_strtoull(argv[3], NULL, 0);
} else { /* argv[2] == NULL */
/* make argv[3] to be a valid thing to fetch */
argv--;
}
if (errno)
bb_show_usage(); /* one of bb_strtouXX failed */
fd = xopen("/dev/mem", argv[3] ? (O_RDWR | O_SYNC) : (O_RDONLY | O_SYNC));-------根据第三个参数确定是以只读形式打开,还是以读写形式打开。/dev/mem代表整个内核空间。
mapped_size = page_size = getpagesize();
offset_in_page = (unsigned)target & (page_size - 1);-----------------------------对地址进行也对齐。
if (offset_in_page + width > page_size) {----------------------------------------如果跨页,则mapped_size编程两个页面。
/* This access spans pages.
* Must map two pages to make it possible: */
mapped_size *= 2;
}
map_base = mmap(NULL,
mapped_size,
argv[3] ? (PROT_READ | PROT_WRITE) : PROT_READ,
MAP_SHARED,
fd,
target & ~(off_t)(page_size - 1));---------------------------------------将/dev/mem文件的从target的页对齐偏移开始,映射mapped_size块大小内存。映射结果是map_base。
if (map_base == MAP_FAILED)
bb_perror_msg_and_die("mmap");
// printf("Memory mapped at address %p.\n", map_base);
virt_addr = (char*)map_base + offset_in_page;