D13. 数据在内存中的存储(二)

本文深入探讨了C语言编程中的一些高级技巧,包括变量操作、浮点数处理、控制台输出控制等,并详细介绍了如何在Windows环境下利用Windows API进行更复杂的编程任务,如控制台窗口的定位和刷新。
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#if 0
int main(){
	int a = 3, b = 4, c = 5;
	while (a < b < c){
		int t = a;
		a = b;
		b = t;
		c--;
	}
	printf("%d,%d,%d", a, b, c);
	system("pause");
	return 0;
}
#endif
#if 0
int main(){
	int a = 3, b = 0, c = 5;
	int n = 0;
	while (--a && ++b && c--)//只要有一个减到0就停止循环
		n++;
	printf("%d,%d,%d,%d\n", a, b, c, n);
	system("pause");
	return 0;
}
#endif
#if 0
int main(){
	int a = 0x11223344;//小端->地位存储在低位地址,高位存储在高位地址
	short int b = a;//分配从高地址到低地址,a高地址b低地址c更低地址
	char c = a;//
	printf("%x,%x,%x\n", a, b, c);//该程序可判断大端小端
	system("pause");
	return 0;
}
#endif
#if 0
int main(){
	int a = 6;
	float b = 4.6;//浮点型存储SEM
	printf("%d\n", (int)b);
	system("pause");
	return 0;
}
#endif
#if 0
int main(){
	int i,  j;
	while (1){
		for (i = 0; i < 10; i++){
			printf("||||||||||");
			Sleep(100);//100ms
			system("cls");
			for (j = 0; j <= i; j++){
				printf("\n");
			}
		}
	}
	system("pause");
	return 0;
}
#endif
#if 1
HANDLE	handle;
COORD pos = { 0, 0 };
int main(){
	int i,  j;
	while (1){
		for (i = 0; i < 10; i++){
			printf("||||||||||");
			Sleep(100);//100ms
			printf("\b\b\b\b\b\b\b\b\b\b");//灯笼来回上下
			printf("          ");
			printf("\n");
		}
		handle = GetStdHandle(STD_OUTPUT_HANDLE);
		SetConsoleCursorPosition(handle, pos);
	}

	system("pause");
	return 0;
}
#endif

 

/* Return the functionality mask */ static inline u32 i2c_get_functionality(struct i2c_adapter *adap) { printk("i2c_get_functionality: enter--------------adap = %p\n", adap); printk("i2c_get_functionality: enter--------------adap->algo->functionality = %p\n", adap->algo->functionality); return adap->algo->functionality(adap); } 挂起栈: [ 13.204509] pc : i2c_dw_func+0x4/0x10 [ 13.208166] lr : i2c_check_functionality+0x5c/0x70 [ 13.212949] sp : ffff800010f7b8d0 [ 13.216255] x29: ffff800010f7b8d0 x28: ffff800010de8cc0 [ 13.221563] x27: ffff8000100bb020 x26: ffff80000887c790 [ 13.226871] x25: ffff800010de8000 x24: ffff000037898364 [ 13.232178] x23: ffff800010d13da0 x22: 0000000000000001 [ 13.237486] x21: ffff800010d13e00 x20: 0000000010000000 [ 13.242793] x19: ffff000037898000 x18: ffff800010c6df88 [ 13.248101] x17: 0000000000000000 x16: 0000000000000000 [ 13.253409] x15: ffff00000134d240 x14: 000000000000018c [ 13.258716] x13: ffff00000134d240 x12: 00000000ffffffea [ 13.264024] x11: ffff800010cddf98 x10: ffff800010cc5f58 [ 13.269331] x9 : ffff800010cc5fb0 x8 : 0000000000017fe8 [ 13.274638] x7 : c0000000ffffefff x6 : 0000000000000001 [ 13.279946] x5 : ffff00003fdc0808 x4 : 0000000000000000 [ 13.285253] x3 : 0000000000000027 x2 : ae2d0d1b69cbfa00 [ 13.290560] x1 : ffff8000105c6918 x0 : 0000000000000000 [ 13.295868] Call trace: [ 13.298311] i2c_dw_func+0x4/0x10 [ 13.301620] i2c_register_adapter+0x16c/0x698 [ 13.305969] __i2c_add_numbered_adapter+0x88/0xa0 [ 13.310666] i2c_add_numbered_adapter+0x3c/0x48 [ 13.315216] i2c_pca954x_register+0x150/0x1f8 [nf_bsp] [ 13.320365] bsp_init+0x78/0x1cc [nf_bsp] [ 13.324370] do_one_initcall+0x50/0x1b0 [ 13.328201] do_init_module+0x50/0x1f0 [ 13.331943] load_module+0x202c/0x2670 [ 13.335685] __do_sys_init_module+0x13c/0x1f0 [ 13.340035] __arm64_sys_init_module+0x18/0x20 [ 13.344474] el0_svc_common.constprop.0+0x7c/0x210 [ 13.349258] do_el0_svc+0x18/0x20 [ 13.352568] el0_svc+0x18/0x48 [ 13.355616] el0_sync_handler+0xa8/0xb0 [ 13.359445] el0_sync+0x15c/0x180 [ 13.362759] Code: 17fffff6 9408e974 d503201f f9407400 (b944c000) 函数中我加了打印 static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func) { printk("i2c_check_functionality: enter--------------adap = %p, func = %d\n", adap, func); return (func & i2c_get_functionality(adap)) == func; } 然后dmesg中打印的是: [ 13.170578] i2c_check_functionality: enter--------------adap = 00000000c4cd0229, func = 268435456 [ 13.170581] i2c_get_functionality: enter--------------adap = 00000000c4cd0229 [ 13.170585] i2c_get_functionality: enter--------------adap->algo->functionality = 00000000f69fc895 [ 13.170599] Unable to handle kernel NULL pointer dereference at virtual address 00000000000004c0 看着指针都是有地址的啊,为什么还是会显示空指针?
最新发布
06-07
<think>我们正在处理一个内核空指针解引用问题。从提供的日志来看,函数`i2c_get_functionality`被调用时,`adap`和`adap->algo->functionality`都有非零的地址值(打印显示为具体指针值),但在执行`adap->algo->functionality(adap)`时发生了空指针解引用。问题发生在地址`0x4c0`处,这个地址非常小(接近0),通常意味着访问了一个未初始化或者已经被释放的结构体中的成员。观察打印:```i2c_get_functionality:enter--------------adap=00000000c4cd0229i2c_get_functionality:enter--------------adap->algo->functionality=00000000f69fc895```但是挂起栈的指令地址在`i2c_dw_func+0x4`,反汇编显示(从Code行)最后一条指令是`b944c000`,这条指令是LDRH(加载半字)指令,格式为`LDRHWt,[Xn,#offset]`。这里机器码`b944c000`分解为:opcode:1011100000...,具体为:1011100:表示LDRH(immediate)00:非符号扩展,32位0:表示非64位加载,使用32位寄存器后面的位是偏移量和寄存器编号。具体说,`b944c000`的进制:1011100101001100000000000000根据ARMv8手册,LDRH(immediate)的格式为:|101110000|imm12|Rn|Rt|这里:opc=00,Rt=00000,Rn=01011,imm12=010011000000->0x4c0所以这条指令实际上是:LDRHW0,[x11,#0x4c0]也就是说,它试图从x11寄存器加上0x4c0的地址处加载一个半字(16位)到w0寄存器。因此,崩溃的原因是x11寄存器的值为0(或者非常小),加上0x4c0后仍然是一个很小的地址(0x4c0),然后访问这个地址,导致空指针解引用。那么x11寄存器从哪里来?在函数`i2c_get_functionality`中,它调用`adap->algo->functionality(adap)`,那么我们可以推断,这个调用是通过函数指针进行的。在ARM64中,函数调用时,第一个参数通过x0传递。所以这里adap应该通过x0传递。而函数指针本身应该存储在某个寄存器中(可能是x8或x16),然后通过BLR指令跳转到该寄存器。但是为什么会出现x11为0的情况呢?请注意,在崩溃现场,寄存器状态显示:x11:ffff800010cddf98但是在崩溃时,x11的值可能已经被破坏?或者我们在打印的时候还没有使用到x11?实际上,崩溃发生在`adap->algo->functionality(adap)`调用内部,即`i2c_dw_func`函数的内部。所以,我们需要分析`i2c_dw_func`函数的实现。从崩溃栈中,我们看到崩溃发生在`i2c_dw_func+0x4`处,而函数的代码只有4个字节?这是不可能的。实际上,崩溃的地址是`i2c_dw_func+0x4`,这意味着函数入口偏移4字节处。我们需要查看该函数的反汇编。由于我们没有函数的完整代码,只能根据上下文推测。问题很可能是由于`adap->algo`或者`adap->algo->functionality`所指向的函数内部访问了一个空指针。但是打印显示`adap->algo->functionality`的值是0xf69fc895,这个值看起来是一个合法的函数地址(内核空间的地址)。然而,在调用这个函数指针时,传入的参数是`adap`(值为0x00000000c4cd0229)。在函数内部,它可能需要访问adap的某些成员,比如可能通过这个adap指针去访问一个偏移量,而这个偏移量在结构体中是一个指针,但该指针为空。从崩溃指令看,它试图访问x11+0x4c0,所以x11必须为空(0)或者是一个小的地址值。从寄存器现场看,x11是ffff800010cddf98,这个值看起来是合理的(内核地址空间)。但是崩溃指令的地址0x4c0很小,说明x11可能被错误覆盖为0了?或者这个x11是从参数adap中派生出来的?注意,参数adap(0xc4cd0229)是一个32位地址(注意:在64位系统中,这样的32位地址可能是有效的,因为内核地址空间有ffffff开头的,也有其他形式的,但0xc4cd0229看起来不像64位全地址,可能是由于打印格式截断?因为打印是%p,但输出只有32位,这很奇怪)。实际上,从打印格式看,我们使用的是%p,这应该打印完整的64位地址。但在日志中,我们看到的adap地址是00000000c4cd0229,这确实是一个64位地址(高32位为0)。因此,adap指针指向一个在低4GB地址空间内的地址。那么,该结构体是否有效?在Linux内核中,大部分内存分配都在高地址(如ffff开头),但低4GB也有可能是用户空间的内存?或者这是一个错误构造的指针。因此,我们怀疑传入的adap指针是无效的。尽管它不为NULL,但是它指向的内存区域可能无效。因为adap->algo的值没有被打印出来,我们只打印了adap->algo->functionality。这可能是adap->algo指向一个非法地址,但打印时却显示了一个值?不,因为打印显示adap->algo->functionality的值不为0,说明adap->algo这个指针是有效的,因为它访问了algo结构体的functionality成员(偏移量)。现在问题是,在调用functionality函数指针时,参数为adap(00000000c4cd0229)。这个地址是有效的吗?实际上,它位于低4GB空间,而内核通常不会将数据结构分配在这个区域。因此,这个指针可能是错误的。进一步,在打印`i2c_check_functionality`时,传入的adap指针也是00000000c4cd0229,但是注意:在32位系统上,这个指针是正常的,但我们这里是64位ARM(arm64),所以地址应该是64位的。为什么打印出来的只有8个字符?可能是由于打印格式的问题。在Linux内核中,%p打印指针的格式会根据指针值进行截断(比如去掉前面的0),但这里打印的是8个字符,所以实际上是32位地址。这不太正常。可能是由于我们在打印时使用了`%p`,但是传入的指针是32位的?或者内核配置了某些选项导致%p打印成短格式?但是,在64位内核中,这个指针应该是64位的。因此,我们怀疑传入的adap指针就是一个32位的值,而高位被忽略,这会导致在64位内核中访问这个地址时,实际访问的是低4GB的地址空间,而这个地址空间可能并未映射有效的内存,从而产生缺页异常?但崩溃日志显示是空指针解引用(NULLpointerdereference),而0x4c0并不等于0,所以实际上应该是访问了无效的内存地址,但内核错误信息说是NULLpointer?其实,Linux内核对于地址低于某一阈值的访问也会报告为NULLpointer解引用(因为接近0,可能是由于访问了一个空指针的某个成员)。所以,问题的根源在于adap指针指向了一个无效的内存区域(低地址区域)。我们需要检查这个adap指针是如何被分配的?从调用栈可以看出,adap来自`i2c_register_adapter`->`__i2c_add_numbered_adapter`->`i2c_add_numbered_adapter`->`i2c_pca954x_register`,在`bsp_init`中调用。因此,我们需要检查在`i2c_pca954x_register`中分配adap结构体的代码。常见错误包括:-在栈上分配结构体,但该结构体在函数返回后被释放。-分配的内存未被正确初始化,尤其是adap->algo成员。-分配的内存被释放后,指针未被置空。由于日志显示adap指针的值为00000000c4cd0229,这个值看起来非常小(32位指针),并且位于32位地址空间,很可能是在驱动中使用了kmalloc分配,但kmalloc在64位系统上返回的地址通常在高位(大于0xffff000000000000)。所以,如果这个adap指针是来自一个32位的值(比如强转自u32),那么就可能出现问题。因此,我们需要检查驱动代码中,adap指针的来源。可能是将结构体指针误存储在一个32位类型的变量中,然后又强制转换回指针?或者传递过程中截断了。另外,也可以通过在`i2c_get_functionality`中打印adap指针的完整64位值(用%px)来确认。但要注意,在内核中,%px用于打印完整的地址(有安全风险,但在调试时可用),而%p可能会被处理(比如使用kptr_restrict)。不过,我们暂时不修改打印,因为已经看到问题。解决方案:1.确认`i2c_pca954x_register`中是如何分配`structi2c_adapter`的。确保使用正确的分配函数(如devm_kzalloc或kzalloc)并且检查分配成功。2.确保在初始化adapter时,正确设置了adap->algo指针,algo结构体应该是一个全局变量(或者生命周期覆盖adapter使用期间)。3.避免将指针强制转换为小于64位的整数类型,特别是在64位系统中。根据调用栈,模块是`nf_bsp`,驱动是`i2c_pca954x_register`。因此,重点检查这个驱动函数。由于我们没有该驱动的源代码,只能从崩溃现象推断。根据我们的分析,很可能是adap指针传递过程中被截断了,或者是分配的内存已经释放。建议的修改:如果发现指针被截断,需要修正存储指针的变量类型(从32位改为64位)。如果是因为内存释放,需要确保在i2c_adapter注销前不能释放。另外,在崩溃指令中,我们看到访问了0x4c0地址,这个地址等于0x4c0。而我们的adap指针是0xc4cd0229,这个地址加上0x4c0是否可能访问到0地址附近?因为0xc4cd0229+0x4c0=0xC4CD06E9,这并不小。所以,崩溃原因应该是函数指针内部错误地使用了另一个空指针(不是adap)。重新考虑:崩溃指令为`LDRHW0,[x11,#0x4c0]`,这时x11的值为0(或者非常小)。那么x11的值应该是0,所以加上0x4c0之后就是0x4c0,这是一个无效地址。那么x11为什么为0?它可能是在函数内部设置的寄存器。x11在ARM64中是通用寄存器。函数内部可能把x11设置为了0,然后访问一个成员(偏移0x4c0)。这需要查看该函数(i2c_dw_func)的代码。所以,我们需要反汇编i2c_dw_func函数,看+4的位置是什么指令。根据崩溃日志中机器码是`b944c000`,这就是崩溃指令(PC指向这里)。函数`i2c_dw_func`的代码只有4个字节?这不可能。崩溃点位于`i2c_dw_func+0x4`,说明函数开头4字节之后就是下一条指令。函数开头可能是一条指令,比如:0x0:movx11,x0//假设0x4:ldrhw0,[x11,#0x4c0]//崩溃在此这里x0就是传入的参数adap(0x00000000c4cd0229)。所以x11被设置为adap的值(0xc4cd0229),然后访问x11+0x4c0,即0xc4cd06e9。这个地址应该在32位地址空间,并不为0,为什么会发生空指针解引用呢?实际上,内核中定义的NULL指针是0地址。而0xc4cd06e9是有效的用户空间地址?但是内核线程访问用户空间地址会导致缺页,而不会报告NULL指针解引用。所以,这里的错误报告“UnabletohandlekernelNULLpointerdereference”是因为该地址是0x4c0,它是在0地址之上0x4c0的地址,所以是NULL指针的偏移。而内核错误报告机制认为任何在PAGE_OFFSET以下的地址(通常0地址开始的一页或多页)都是NULL指针区域,因此会报告NULL指针解引用。但是,0xc4cd06e9这个地址显然大于0x4c0,所以它不应该在NULL指针区域(NULL指针区域一般是从0地址开始的一页或几页)。那么,为什么这个地址被认为是NULL指针解引用呢?可能是因为在ARM64中,0地址到USER_ZSIZE(用户空间大小)是用户空间,而内核空间从VA_START(例如0xffff000000000000)开始。所以0xc4cd06e9是一个用户空间地址,但是内核线程不允许访问用户空间,所以会触发一个错误。但是,这个错误一般应该报告为“pagefault”或者“unabletoaccessuseraddressinkernel”,而不是“NULLpointer”。不过,由于这个地址在物理内存上可能未映射,所以MMU触发了一个页错误异常。在异常处理中,检查到该地址在0到某个阈值(high_memory的物理映射?)之间,所以报告为NULLpointer解引用。实际上,在内核代码中,对于任何低于PAGE_OFFSET(比如0xffff800000000000)的地址且不处于内核线性映射区域的地址,都可能会被报告为类似错误。综上所述,问题在于adap指针指向了一个用户空间的地址(0xc4cd0229),这个地址在内核态访问是非法的。所以,我们需要检查adap指针的来源,确保它指向内核地址空间。因此,请检查创建adapter的驱动代码,确保使用内核分配函数(如kzalloc)来分配adapter结构,并确保分配后的指针被正确使用(没有被强制转成32位整数等)。总结:这是一个传入非法指针(用户空间地址)导致的问题。需要检查驱动中创建i2c_adapter的代码。解决步骤:1.在驱动代码中,找到分配i2c_adapter结构体的位置,确保使用kzalloc/devm_kzalloc,并且检查返回值。2.确保分配得到的指针不截断存储。3.确保初始化adap->algo为有效且内核空间的algo结构体(通常是驱动内部的全局变量)。4.如果使用栈上的变量,要改为动态分配(因为栈变量在函数返回后释放)。由于没有提供驱动代码,只能给出方向性建议。相关问题:§§相关问题§§:1.在Linux内核中,如何正确分配一个i2c_adapter结构体?2.在Linux驱动中,初始化i2c_adapter结构体时,algo指针应该指向什么?3.为什么在内核中传入用户空间的指针会导致NULL指针解引用错误?4.如何在内核代码中调试类似的内存访问错误?</think> ### 问题分析 问题出现在内核驱动中,您观察到 `adap` 和 `adap->algo->functionality` 指针非零(打印显示有效地址),但在调用 `adap->algo->functionality(adap)` 时仍触发空指针解引用。根本原因是**传入的 `adap` 指针指向用户空间地址(`0xc4cd0229`),而非内核合法地址**。细节如下: 1. **指针有效性假象**: - 打印显示 `adap = 0xc4cd0229` 和 `adap->algo->functionality = 0xf69fc895` 是非零值,但: - `0xc4cd0229` 位于低4GB地址空间(用户空间),**内核无法直接访问**。 - `adap->algo->functionality` 的"有效地址"可能是巧合(未初始化内存的随机值),实际指向非法区域。 2. **崩溃直接原因**: - 执行 `adap->algo->functionality(adap)` 时,函数内部尝试通过寄存器 `x11` 访问偏移 `0x4c0`: ```asm LDRH W0, [X11, #0x4c0] ; 崩溃指令 ``` - `x11` 可能由参数 `adap` 赋值而来(`0xc4cd0229`)。但该地址在内核态无效,触发"NULL指针解引用"(内核将低地址访问统一归类为NULL指针错误)。 3. **根本原因**: - **`adap` 指针被截断或错误传递**: - `i2c_pca954x_register` 驱动中可能将内核指针误存为32位整数(如 `u32`),再强转为指针,导致高32位丢失。 - 或指针来自未初始化的栈/堆内存,指向残留的用户空间地址。 4. **调用栈印证**: - 崩溃发生在 `i2c_dw_func`(`adap->algo->functionality` 指向的函数),源于 `i2c_pca954x_register` 驱动初始化流程,表明问题在驱动代码。 --- ### 解决方法 #### 步骤1:修复指针传递 在驱动中检查 `struct i2c_adapter *adap` 的来源: ```c // 示例:正确分配 i2c_adapter(在 i2c_pca954x_register 或相关函数中) struct i2c_adapter *adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); if (!adap) { dev_err(dev, "Failed to allocate adapter\n"); return -ENOMEM; } // 初始化 algo 指针(假设 i2c_dw_algo 是全局常量) adap->algo = &i2c_dw_algo; // 确保 algo 指向有效内核地址 // 避免指针截断!所有指针类型用完整类型传递。 ``` #### 步骤2:增强调试 在 `i2c_check_functionality` 中改用 `%px` 打印完整地址: ```c printk("i2c_check_functionality: adap = %px, func = %x\n", adap, func); ``` - `%px` 打印64位全地址,避免 `%p` 截断(可能显示为8字符伪值)。 #### 步骤3:验证内存生命周期 确保 `adap` 和 `adap->algo` **不指向栈内存**: ```c // 错误:栈变量在函数返回后失效 static int register_adapter(void) { struct i2c_adapter adap; // 栈分配 → 崩溃根源! i2c_add_adapter(&adap); // 返回后指针失效 } // 正确:动态分配 static int register_adapter(void) { struct i2c_adapter *adap = kzalloc(sizeof(*adap), GFP_KERNEL); i2c_add_adapter(adap); // 需在卸载时释放 } ``` #### 步骤4:检查驱动逻辑 重点检查 `i2c_pca954x_register`: - 是否用 `kzalloc/devm_kzalloc` 分配 `adap`? - 是否误用 `uintptr_t/u32` 存储指针后再强转? - `adap->algo` 是否初始化?指向的 algo 结构是否全局有效? --- ### 总结 - **核心问题**:`adap` 指针虽非零,但指向非法区域(用户空间地址),导致内核访问崩溃。 - **修复方向**:确保指针来源合法(内核分配)、完整传递(64位)、生命周期正确。 - **调试技巧**:用 `%px` 打印地址,结合 `objdump -d` 反汇编崩溃函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值