.. SPDX-License-Identifier: GPL-2.0 .. include:: ../disclaimer-zh_CN.rst :Original: Documentation/dev-tools/kmemleak.rst :Translator: 刘浩阳 Haoyang Liu <tttturtleruss@hust.edu.cn> å†…æ ¸å†…å˜æ³„露检测器 ================== Kmemleak æä¾›äº†ä¸€ä¸ªç±»ä¼¼ `å¯è¿½è¸ªçš„垃圾收集器 <https://en.wikipedia.org/wiki/Tra cing_garbage_collection>`_ çš„æ–¹æ³•æ¥æ£€æµ‹å¯èƒ½çš„å†…æ ¸å†…å˜æ³„æ¼ï¼Œä¸åŒçš„æ˜¯å¤ç«‹å¯¹è±¡ä¸ä¼š 被释放,而是仅通过 /sys/kernel/debug/kmemleak 报告。Valgrind 工具 (``memcheck --leak-check``)使用了一ç§ç›¸ä¼¼çš„æ–¹æ³•æ¥æ£€æµ‹ç”¨æˆ·ç©ºé—´åº”用ä¸çš„å†…å˜æ³„ 露。 用法 ---- "Kernel hacking" ä¸çš„ CONFIG_DEBUG_KMEMLEAK 必须被å¯ç”¨ã€‚ä¸€ä¸ªå†…æ ¸çº¿ç¨‹æ¯10分钟 (默认情况下)扫æä¸€æ¬¡å†…å˜ï¼Œå¹¶ä¸”打å°å‡ºæ–°å‘现的未被引用的对象个数。 如果 ``debugfs`` 没有挂载,则执行:: # mount -t debugfs nodev /sys/kernel/debug/ 显示所有扫æå‡ºçš„å¯èƒ½çš„å†…å˜æ³„æ¼çš„细节信æ¯:: # cat /sys/kernel/debug/kmemleak å¯åŠ¨ä¸€æ¬¡ä¸ç‰ç¨‹åº¦çš„å†…å˜æ‰«æ:: # echo scan > /sys/kernel/debug/kmemleak æ¸…ç©ºå½“å‰æ‰€æœ‰å¯èƒ½çš„å†…å˜æ³„露列表:: # echo clear > /sys/kernel/debug/kmemleak 当冿¬¡è¯»å– ``/sys/kernel/debug/kmemleak`` 文件时,将会输出自上次扫æä»¥æ¥æ£€æµ‹åˆ°çš„ æ–°çš„å†…å˜æ³„露。 注æ„,å¤ç«‹ç›®æ ‡æ˜¯é€šè¿‡è¢«åˆ†é…æ—¶é—´æ¥æŽ’åºçš„,列表开始的对象å¯èƒ½ä¼šå¯¼è‡´åŽç»çš„对象都被 识别为å¤ç«‹å¯¹è±¡ã€‚ å¯ä»¥é€šè¿‡å†™å…¥ ``/sys/kernel/debug/kmemleak`` 文件在è¿è¡Œæ—¶ä¿®æ”¹å†…å˜æ‰«æå‚æ•°ã€‚ä¸‹é¢æ˜¯ 支æŒçš„傿•°ï¼š * off ç¦ç”¨ kmemleak(ä¸å¯é€†ï¼‰ * stack=on å¼€å¯ä»»åŠ¡æ ˆæ‰«æï¼ˆé»˜è®¤ï¼‰ * stack=off ç¦ç”¨ä»»åŠ¡æ ˆæ‰«æ * scan=on å¼€å¯è‡ªåŠ¨å†…å˜æ‰«æçº¿ç¨‹ï¼ˆé»˜è®¤ï¼‰ * scan=off å…³é—è‡ªåŠ¨å†…å˜æ‰«æçº¿ç¨‹ * scan=<secs>; è®¾å®šè‡ªåŠ¨å†…å˜æ‰«æé—´éš”,以秒为å•ä½ï¼ˆé»˜è®¤å€¼ä¸º 600,设置为 0 è¡¨ç¤ºåœ æ¢è‡ªåŠ¨æ‰«æï¼‰ * scan 触å‘ä¸€æ¬¡å†…å˜æ‰«æ * clear é€šè¿‡æ ‡è®°æ‰€æœ‰å½“å‰å·²æŠ¥å‘Šçš„æœªè¢«å¼•用对象为ç°ï¼Œä»Žè€Œæ¸…空当å‰å¯èƒ½çš„å†…å˜æ³„露列 表;如果 kmemleak 被ç¦ç”¨ï¼Œåˆ™é‡Šæ”¾æ‰€æœ‰ kmemleak 对象,。 * dump=<addr> 输出å˜å‚¨åœ¨ <addr> ä¸çš„å¯¹è±¡ä¿¡æ¯ å¯ä»¥é€šè¿‡åœ¨å†…æ ¸å‘½ä»¤è¡Œä¸ä¼ 递 ``kmemleak=off`` 傿•°ä»Žè€Œåœ¨å¯åŠ¨æ—¶ç¦ç”¨ Kmemleak。 在 kmemleak åˆå§‹åŒ–之å‰å°±å¯èƒ½ä¼šæœ‰å†…å˜åˆ†é…或释放,这些æ“作被å˜å‚¨åœ¨ä¸€ä¸ªæ—©æœŸæ—¥å¿—缓 冲区ä¸ã€‚缓冲区的大å°é€šè¿‡ CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE 选项é…置。 如果 CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF 被å¯ç”¨ï¼Œåˆ™ kmemleak 默认被ç¦ç”¨ã€‚åœ¨å†…æ ¸å‘½ 令行ä¸ä¼ 递 ``kmemleak=on`` 傿•°æ¥å¼€å¯è¿™ä¸ªåŠŸèƒ½ã€‚ 如果出现 "Error while writing to stdout" 或 "write_loop: Invalid argument" è¿™æ · 的错误,请确认 kmemleak 被æ£ç¡®å¯ç”¨ã€‚ 基础算法 -------- 通过 :c:func:`kmalloc`, :c:func:`vmalloc`, :c:func:`kmem_cache_alloc` 以åŠåŒç±» 函数å‡è¢«è·Ÿè¸ªï¼ŒæŒ‡é’ˆï¼ŒåŒ…括一些é¢å¤–的信æ¯å¦‚大å°å’Œæ ˆè¿½è¸ªç‰ï¼Œéƒ½è¢«å˜å‚¨åœ¨çº¢é»‘æ ‘ä¸ã€‚ 对应的释放函数调用也被追踪,并从 kmemleak æ•°æ®ç»“æž„ä¸ç§»é™¤ç›¸åº”指针。 对于一个已分é…的内å˜å—,如果通过扫æå†…å˜ï¼ˆåŒ…括ä¿å˜å¯„å˜å™¨ï¼‰æ²¡æœ‰å‘çŽ°ä»»ä½•æŒ‡é’ˆæŒ‡å‘ å®ƒçš„èµ·å§‹åœ°å€æˆ–者其ä¸çš„任何ä½ç½®ï¼Œåˆ™è®¤ä¸ºè¿™å—å†…å˜æ˜¯å¤ç«‹çš„。这æ„味ç€å†…æ ¸æ— æ³•å°†è¯¥å†… å˜å—的地å€ä¼ 递给一个释放内å˜å‡½æ•°ï¼Œè¿™å—内å˜ä¾¿è¢«è®¤ä¸ºæ³„露了。 扫æç®—法æ¥éª¤ï¼š 1. æ ‡è®°æ‰€æœ‰å¯¹è±¡ä¸ºç™½è‰²ï¼ˆæœ€åŽå‰©ä¸‹çš„白色对象被认为是å¤ç«‹çš„) 2. 从数æ®èŠ‚å’Œæ ˆå¼€å§‹æ‰«æå†…å˜ï¼Œæ£€æµ‹æ¯ä¸ªå€¼æ˜¯å¦æ˜¯çº¢é»‘æ ‘ä¸å˜å‚¨çš„地å€ã€‚å¦‚æžœä¸€ä¸ªæŒ‡å‘ ç™½è‰²å¯¹è±¡çš„æŒ‡é’ˆè¢«æ£€æµ‹åˆ°ï¼Œåˆ™å°†è¯¥å¯¹è±¡æ ‡è®°ä¸ºç°è‰²ã€‚ 3. 扫æç°è‰²å¯¹è±¡å¼•用的其他对象(有些白色对象å¯èƒ½ä¼šå˜ä¸ºç°è‰²å¹¶è¢«æ·»åŠ åˆ°ç°å啿œ«å°¾ )直到ç°åå•为空。 4. 剩余的白色对象就被认为是å¤ç«‹çš„并通过 /sys/kernel/debug/kmemleak 报告。 有些指å‘已分é…的内å˜å—的指针å˜å‚¨åœ¨å†…æ ¸å†…éƒ¨çš„æ•°æ®ç»“æž„ä¸ï¼Œå®ƒä»¬ä¸èƒ½è¢«æ£€æµ‹ä¸ºå¤ç«‹ã€‚ 为了é¿å…è¿™ç§æƒ…况,kmemleak 也å˜å‚¨äº†æŒ‡å‘需è¦è¢«æŸ¥æ‰¾çš„内å˜å—范围内的任æ„地å€çš„åœ°å€ æ•°é‡ï¼Œå¦‚æ¤ä¸€æ¥è¿™äº›å†…å˜ä¾¿ä¸ä¼šè¢«è®¤ä¸ºæ³„éœ²ã€‚ä¸€ä¸ªä¾‹åæ˜¯ __vmalloc()。 用 kmemleak 测试特定部分 ------------------------ 在åˆå§‹åŒ–å¯åŠ¨é˜¶æ®µ /sys/kernel/debug/kmemleak 的输出å¯èƒ½ä¼šå¾ˆå¤šï¼Œè¿™ä¹Ÿå¯èƒ½æ˜¯ä½ åœ¨å¼€å‘ æ—¶ç¼–å†™çš„æ¼æ´žç™¾å‡ºçš„代ç å¯¼è‡´çš„ã€‚ä¸ºäº†è§£å†³è¿™ç§æƒ…å†µä½ å¯ä»¥ä½¿ç”¨ 'clear' å‘½ä»¤æ¥æ¸…除 /sys/kernel/debug/kmemleak 输出的所有的未引用对象。在执行 'clear' åŽæ‰§è¡Œ 'scan' å¯ä»¥å‘çŽ°æ–°çš„æœªå¼•ç”¨å¯¹è±¡ï¼Œè¿™å°†ä¼šæœ‰åˆ©ä½ æµ‹è¯•ä»£ç 的特定部分。 为了用一个空的 kmemleak 测试一个特定部分,执行:: # echo clear > /sys/kernel/debug/kmemleak ... æµ‹è¯•ä½ çš„å†…æ ¸æˆ–è€…æ¨¡å— ... # echo scan > /sys/kernel/debug/kmemleak ç„¶åŽåƒå¹³å¸¸ä¸€æ ·èŽ·å¾—æŠ¥å‘Š:: # cat /sys/kernel/debug/kmemleak 释放 kmemleak å†…æ ¸å¯¹è±¡ ---------------------- 为了å…许访问先å‰å‘çŽ°çš„å†…å˜æ³„露,当用户ç¦ç”¨æˆ–å‘生致命错误导致 kmemleak 被ç¦ç”¨æ—¶ï¼Œå†…æ ¸ä¸çš„ kmemleak 对象ä¸ä¼šè¢«é‡Šæ”¾ã€‚这些对象å¯èƒ½ä¼šå 用很大 一部分物ç†å†…å˜ã€‚ åœ¨è¿™ç§æƒ…å†µä¸‹ï¼Œä½ å¯ä»¥ç”¨å¦‚下命令回收这些内å˜:: # echo clear > /sys/kernel/debug/kmemleak Kmemleak API ------------ 在 include/linux/kmemleak.h å¤´æ–‡ä»¶ä¸æŸ¥çœ‹å‡½æ•°åŽŸåž‹ï¼š - ``kmemleak_init`` - åˆå§‹åŒ– kmemleak - ``kmemleak_alloc`` - 通知一个内å˜å—çš„åˆ†é… - ``kmemleak_alloc_percpu`` - 通知一个 percpu 类型的内å˜åˆ†é… - ``kmemleak_vmalloc`` - 通知一个使用 vmalloc() 的内å˜åˆ†é… - ``kmemleak_free`` - 通知一个内å˜å—的释放 - ``kmemleak_free_part`` - 通知一个部分的内å˜é‡Šæ”¾ - ``kmemleak_free_percpu`` - 通知一个 percpu 类型的内å˜é‡Šæ”¾ - ``kmemleak_update_trace`` - 更新分é…å¯¹è±¡è¿‡ç¨‹çš„æ ˆè¿½è¸ª - ``kmemleak_not_leak`` - æ ‡è®°ä¸€ä¸ªå¯¹è±¡å†…å˜ä¸ºæœªæ³„露的 - ``kmemleak_ignore`` - ä¸è¦æ‰«ææˆ–报告æŸä¸ªå¯¹è±¡æœªæ³„露的 - ``kmemleak_scan_area`` - 在内å˜å—䏿·»åŠ æ‰«æåŒºåŸŸ - ``kmemleak_no_scan`` - 䏿‰«ææŸä¸ªå†…å˜å— - ``kmemleak_erase`` - 在指针å˜é‡ä¸ç§»é™¤æŸä¸ªæ—§çš„值 - ``kmemleak_alloc_recursive`` - å’Œ kmemleak_alloc 效果相åŒä½†ä¼šæ£€æŸ¥æ˜¯å¦æœ‰é€’å½’çš„ 内å˜åˆ†é… - ``kmemleak_free_recursive`` - å’Œ kmemleak_free 效果相åŒä½†ä¼šæ£€æŸ¥æ˜¯å¦æœ‰é€’å½’çš„ 内å˜é‡Šæ”¾ 下列函数使用一个物ç†åœ°å€ä½œä¸ºå¯¹è±¡æŒ‡é’ˆå¹¶ä¸”åªåœ¨åœ°å€æœ‰ä¸€ä¸ª lowmem æ˜ å°„æ—¶åšå‡ºç›¸åº”çš„ 行为: - ``kmemleak_alloc_phys`` - ``kmemleak_free_part_phys`` - ``kmemleak_ignore_phys`` 解决å‡é˜³æ€§/å‡é˜´æ€§ ----------------- å‡é˜´æ€§æ˜¯æŒ‡ç”±äºŽåœ¨å†…å˜æ‰«æä¸æœ‰å€¼æŒ‡å‘该对象导致 kmemleak 没有报告的实际å˜åœ¨çš„å†…å˜ æ³„éœ²ï¼ˆå¤ç«‹å¯¹è±¡ï¼‰ã€‚为了å‡å°‘å‡é˜´æ€§çš„出现次数,kmemleak æä¾›äº† kmemleak_ignore, kmemleak_scan_area,kmemleak_no_scan å’Œ kmemleak_erase 函数(è§ä¸Šï¼‰ã€‚ ä»»åŠ¡æ ˆä¹Ÿä¼šå¢žåŠ å‡é˜´æ€§çš„æ•°é‡å¹¶ä¸”默认ä¸å¼€å¯å¯¹å®ƒä»¬çš„æ‰«æã€‚ å‡é˜³æ€§æ˜¯å¯¹è±¡è¢«è¯¯æŠ¥ä¸ºå†…å˜æ³„露(å¤ç«‹å¯¹è±¡ï¼‰ã€‚对于已知未泄露的对象,kmemleak æä¾›äº† kmemleak_not_leak å‡½æ•°ã€‚åŒæ—¶ kmemleak_ignore å¯ä»¥ç”¨äºŽæ ‡è®°å·²çŸ¥ä¸åŒ…å«ä»»ä½• 其他指针的内å˜å—ï¼Œæ ‡è®°åŽè¯¥å†…å˜å—ä¸ä¼šå†è¢«æ‰«æã€‚ 一些被报告的泄露仅仅是暂时的,尤其是在 SMP(对称多处ç†ï¼‰ç³»ç»Ÿä¸ï¼Œå› 为其指针 æš‚å˜åœ¨ CPU 寄å˜å™¨æˆ–æ ˆä¸ã€‚Kmemleak 定义了 MSECS_MIN_AGE(默认值为 1000) æ¥è¡¨ç¤ºä¸€ä¸ªè¢«æŠ¥å‘Šä¸ºå†…å˜æ³„露的对象的最å°å˜æ´»æ—¶é—´ã€‚ é™åˆ¶å’Œç¼ºç‚¹ ---------- 主è¦çš„缺点是内å˜åˆ†é…和释放的性能下é™ã€‚为了é¿å…å…¶ä»–çš„æŸå¤±ï¼Œåªæœ‰å½“ /sys/kernel/debug/kmemleak æ–‡ä»¶è¢«è¯»å–æ—¶æ‰ä¼šè¿›è¡Œå†…å˜æ‰«æã€‚æ— è®ºå¦‚ä½•ï¼Œè¿™ä¸ªå·¥å…·æ˜¯å‡ºäºŽ è°ƒè¯•çš„ç›®æ ‡ï¼Œæ€§èƒ½è¡¨çŽ°å¯èƒ½ä¸æ˜¯æœ€é‡è¦çš„。 ä¸ºäº†ä¿æŒç®—法简å•,kmemleak å¯»æ‰¾æŒ‡å‘æŸä¸ªå†…å˜å—范围ä¸çš„任何值。这å¯èƒ½ä¼šå¼•å‘å‡é˜´æ€§ 现象的出现。但是,最åŽä¸€ä¸ªçœŸæ£çš„å†…å˜æ³„露也会å˜å¾—明显。 éžæŒ‡é’ˆå€¼çš„æ•°æ®æ˜¯å‡é˜´æ€§çš„å¦ä¸€ä¸ªæ¥æºã€‚在将æ¥çš„版本ä¸ï¼Œkmemleak 仅仅会扫 æå·²åˆ†é…结构体ä¸çš„æŒ‡é’ˆæˆå‘˜ã€‚这个特性会解决上述很多的å‡é˜´æ€§æƒ…况。 Kmemleak 会报告å‡é˜³æ€§ã€‚è¿™å¯èƒ½å‘生在æŸäº›è¢«åˆ†é…的内å˜å—ä¸éœ€è¦è¢«é‡Šæ”¾çš„æƒ…况下 (æŸäº› init_call 函数ä¸ï¼‰ï¼ŒæŒ‡é’ˆçš„è®¡ç®—æ˜¯é€šè¿‡å…¶ä»–æ–¹æ³•è€Œä¸æ˜¯å¸¸è§„çš„ container_of å® æˆ–æ˜¯æŒ‡é’ˆè¢«å˜å‚¨åœ¨ kmemleak 没有扫æçš„地方。 页分é…å’Œ ioremap ä¸ä¼šè¢«è¿½è¸ªã€‚ 使用 kmemleak-test 测试 ----------------------- ä¸ºäº†æ£€æµ‹æ˜¯å¦æˆåŠŸå¯ç”¨äº† kmemleakï¼Œä½ å¯ä»¥ä½¿ç”¨ä¸€ä¸ªæ•…æ„åˆ¶é€ å†…å˜æ³„éœ²çš„æ¨¡å— kmemleak-test。设置 CONFIG_SAMPLE_KMEMLEAK 为模å—(ä¸èƒ½ä½œä¸ºå†…建模å—使用) 并且å¯åЍå¯ç”¨äº† kmemleak çš„å†…æ ¸ã€‚åŠ è½½æ¨¡å—并执行一次扫æ:: # modprobe kmemleak-test # echo scan > /sys/kernel/debug/kmemleak 注æ„ä½ å¯èƒ½æ— 法立刻或在第一次扫æåŽå¾—到结果。当 kmemleak 得到结果,将会输出日 å¿— ``kmemleak: <count of leaks> new suspected memory leaks`` 。然åŽé€šè¿‡è¯»å–文件 获å–ä¿¡æ¯:: # cat /sys/kernel/debug/kmemleak unreferenced object 0xffff89862ca702e8 (size 32): comm "modprobe", pid 2088, jiffies 4294680594 (age 375.486s) hex dump (first 32 bytes): 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 kkkkkkkkkkkkkkk. backtrace: [<00000000e0a73ec7>] 0xffffffffc01d2036 [<000000000c5d2a46>] do_one_initcall+0x41/0x1df [<0000000046db7e0a>] do_init_module+0x55/0x200 [<00000000542b9814>] load_module+0x203c/0x2480 [<00000000c2850256>] __do_sys_finit_module+0xba/0xe0 [<000000006564e7ef>] do_syscall_64+0x43/0x110 [<000000007c873fa6>] entry_SYSCALL_64_after_hwframe+0x44/0xa9 ... 用 ``rmmod kmemleak_test`` ç§»é™¤æ¨¡å—æ—¶ä¹Ÿä¼šè§¦å‘ kmemleak 的结果输出。