├── kLLDB.sh ├── kLLDB.py ├── CMakeLists.txt ├── plugins ├── CMakeLists.txt ├── kLLDBLive.cpp └── kLLDBOffline.cpp ├── scripts └── report-linux-bug.py ├── README.md └── LICENSE /kLLDB.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR="$(dirname "$0")" 3 | PYTHON_SCRIPT="$SCRIPT_DIR/kLLDB.py" 4 | 5 | # Launch LLDB and import the initialization script 6 | lldb -o "command script import $PYTHON_SCRIPT" "$@" 7 | 8 | -------------------------------------------------------------------------------- /kLLDB.py: -------------------------------------------------------------------------------- 1 | # kLLDB by djolertrk 2 | 3 | import lldb 4 | import os 5 | 6 | def __lldb_init_module(debugger, internal_dict): 7 | # Set the custom prompt for kLLDB 8 | debugger.HandleCommand("settings set prompt 'kLLDB> '") 9 | 10 | # Get the directory of this script (should be in bin/) 11 | script_dir = os.path.dirname(__file__) 12 | 13 | # Construct the path to the plugin in ../lib/ 14 | plugin_path = os.path.join(script_dir, "..", "lib", "libkLLDBLive.so") 15 | plugin_path = os.path.abspath(plugin_path) 16 | 17 | # Load the plugin 18 | load_command = f"plugin load {plugin_path}" 19 | debugger.HandleCommand(load_command) 20 | 21 | print(f"kLLDB: Plugin loaded from {plugin_path}") 22 | print("kLLDB: Ready") 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.4) 2 | project(kLLDB) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | find_package(LLVM REQUIRED CONFIG) 8 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 9 | include(AddLLVM) 10 | 11 | include_directories(${LLVM_INCLUDE_DIRS}) 12 | separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) 13 | add_definitions(${LLVM_DEFINITIONS_LIST}) 14 | 15 | add_subdirectory(plugins) 16 | 17 | add_custom_target(copy_kLLDB_scripts ALL 18 | COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin" 19 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 20 | "${CMAKE_SOURCE_DIR}/kLLDB.sh" "${CMAKE_BINARY_DIR}/bin/kLLDB" 21 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 22 | "${CMAKE_SOURCE_DIR}/kLLDB.py" "${CMAKE_BINARY_DIR}/bin/kLLDB.py" 23 | COMMENT "Copying kLLDB.sh and kLLDB.py into build/bin" 24 | ) 25 | 26 | add_dependencies(copy_kLLDB_scripts kLLDBLive) 27 | 28 | install(TARGETS kLLDBLive 29 | LIBRARY DESTINATION lib 30 | RUNTIME DESTINATION bin 31 | ) 32 | 33 | install(PROGRAMS 34 | "${CMAKE_SOURCE_DIR}/kLLDB.sh" 35 | DESTINATION bin 36 | RENAME kLLDB 37 | ) 38 | 39 | install(FILES 40 | "${CMAKE_SOURCE_DIR}/kLLDB.py" 41 | DESTINATION bin 42 | ) 43 | -------------------------------------------------------------------------------- /plugins/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 2 | 3 | find_library(LLDB_LIBRARY 4 | NAMES lldb LLDB 5 | HINTS /usr/lib/llvm-19/lib/ 6 | ) 7 | 8 | include_directories(/usr/lib/llvm-19/include/) 9 | 10 | add_library(kLLDBLive SHARED kLLDBLive.cpp) 11 | 12 | set_target_properties(kLLDBLive PROPERTIES 13 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 14 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 15 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 16 | ) 17 | 18 | find_library(KDUMPFILE_LIBRARY 19 | NAMES libkdumpfile.a 20 | HINTS /usr/local/lib /usr/lib 21 | ) 22 | if(NOT KDUMPFILE_LIBRARY) 23 | message(FATAL_ERROR "Could not find libkdumpfile! Provide a HINTS or PATHS.") 24 | endif() 25 | 26 | find_library(ADDRXLAT_LIBRARY 27 | NAMES libaddrxlat.a 28 | HINTS /usr/local/lib /usr/lib 29 | ) 30 | if(NOT ADDRXLAT_LIBRARY) 31 | message(FATAL_ERROR "Could not find libaddrxlat! Provide a HINTS or PATHS.") 32 | endif() 33 | 34 | message(STATUS "Found libkdumpfile at: ${KDUMPFILE_LIBRARY}") 35 | message(STATUS "Found libaddrxlat at: ${ADDRXLAT_LIBRARY}") 36 | 37 | add_library(kLLDBOffline SHARED kLLDBOffline.cpp) 38 | 39 | target_link_libraries(kLLDBOffline PRIVATE 40 | ${LLDB_LIBRARY} 41 | ${KDUMPFILE_LIBRARY} 42 | ${ADDRXLAT_LIBRARY} 43 | z 44 | ) 45 | 46 | set_target_properties(kLLDBOffline PROPERTIES 47 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 48 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 49 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 50 | ) 51 | 52 | install(TARGETS kLLDBLive 53 | LIBRARY DESTINATION lib 54 | RUNTIME DESTINATION bin 55 | ) 56 | 57 | install(TARGETS kLLDBOffline 58 | LIBRARY DESTINATION lib 59 | RUNTIME DESTINATION bin 60 | ) 61 | -------------------------------------------------------------------------------- /scripts/report-linux-bug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import subprocess 3 | import re 4 | import os 5 | import argparse 6 | import contextlib 7 | import sys 8 | 9 | try: 10 | from llama_cpp import Llama 11 | except ImportError: 12 | print("llama_cpp not installed. Please install with: pip install llama-cpp-python") 13 | raise 14 | 15 | ANSI_RE = re.compile(r"\x1b\[[0-9;]*m") 16 | 17 | def generate_bug_report_locally(analysis_data, llm_path): 18 | prompt_text = f"""You are Linux Kernel expert. You are analyzing a Linux kernel crash and here are some inputs about crash: 19 | 20 | Details: 21 | Blame Module: {analysis_data['module']} 22 | Instruction Pointer (RIP): {analysis_data['rip']} 23 | Function: {analysis_data['function']} 24 | 25 | Register Dump: 26 | {analysis_data['registers']} 27 | 28 | Additional Info: 29 | {analysis_data['info']} 30 | 31 | Source Snippet (kv_reset_tainted): 32 | {analysis_data['source_code']} 33 | 34 | === 35 | 36 | Provide a short and concise bug report describing: 37 | - The likely cause of the crash. 38 | - Which module and function are implicated. 39 | - Check function arguments and registers (e.g. arg one is RDI, second is RDI, etc.). 40 | - Potential next steps or debugging strategies. 41 | - Be short. 42 | - Should be in up to 2 sentences. 43 | - Print summary only. 44 | - Do not print process! 45 | === 46 | """ 47 | print("== Prompt used:") 48 | print(prompt_text) 49 | 50 | # Hide llama-cpp logs by redirecting stdout/stderr 51 | with open(os.devnull, 'w') as devnull: 52 | with contextlib.redirect_stdout(devnull), contextlib.redirect_stderr(devnull): 53 | llm = Llama( 54 | model_path=llm_path, 55 | n_ctx=2048, 56 | n_gpu_layers=0, 57 | n_threads=4, 58 | f16_kv=False, 59 | use_mlock=False 60 | ) 61 | output = llm( 62 | prompt=prompt_text, 63 | max_tokens=512, 64 | temperature=0.2, 65 | stop=[""] 66 | ) 67 | 68 | if "choices" in output and output["choices"]: 69 | return output["choices"][0]["text"].strip() 70 | return output.get("data", "").strip() 71 | 72 | 73 | def main(): 74 | parser = argparse.ArgumentParser() 75 | parser.add_argument("--llm-path", required=True) 76 | parser.add_argument("--lldb-path", default="/usr/bin/lldb") 77 | parser.add_argument("--kdump-plugin-path", default="/path/to/libkLLDBLinux.so") 78 | parser.add_argument("--vmlinux-path", default="/path/to/vmlinux") 79 | parser.add_argument("--crash-file-path", default="./crash.file") 80 | parser.add_argument("--lkm-path", default="kovid.ko") 81 | parser.add_argument("--source-dir-old", default="/kovid/", 82 | help="Old source dir path inside the kernel debug info.") 83 | parser.add_argument("--source-dir-new", default="/path/to/KoviD/kovid", 84 | help="New source dir path on your filesystem.") 85 | parser.add_argument("--report-file", default=None, 86 | help="Path to save the generated bug report (if provided). Otherwise prints to stdout.") 87 | 88 | args = parser.parse_args() 89 | 90 | lldb_cmd = [ 91 | args.lldb_path, 92 | "-o", f"target create {args.vmlinux_path}", 93 | "-o", f"plugin load {args.kdump_plugin_path}", 94 | "-o", f"kdump open {args.crash_file_path}", 95 | "-o", "kdump info", 96 | "-o", f"kdump load-lkm {args.lkm_path}", 97 | "-o", "kdump bugpoint", 98 | "-o", f"kdump source-dir-map {args.source_dir_old} {args.source_dir_new}", 99 | "-o", "list kv_reset_tainted", 100 | "-o", "kdump registers", 101 | "-o", "q" 102 | ] 103 | 104 | print("Running LLDB with one-shot commands...") 105 | print(" ".join(lldb_cmd)) 106 | 107 | # Run the command, capture output 108 | result = subprocess.run(lldb_cmd, capture_output=True, text=True) 109 | full_output = result.stdout 110 | 111 | if result.returncode != 0: 112 | print("WARNING: LLDB returned a non-zero status!", result.returncode) 113 | 114 | # Strip ANSI color codes 115 | clean_output = ANSI_RE.sub("", full_output) 116 | 117 | # --- PARSE BUGPOINT --- 118 | blame_module = re.search(r"Blame module:\s*(.*)", clean_output) 119 | rip_address = re.search(r"Instructon pointer at rip=(0x[0-9A-Fa-f]+)", clean_output) 120 | blame_func = re.search(r"Blame function:\s*(.*)", clean_output) 121 | 122 | # --- PARSE REGISTERS --- 123 | registers = {} 124 | for line in clean_output.splitlines(): 125 | line_strip = line.strip() 126 | # Example: " RAX=0x0000..." 127 | if "=" in line_strip and re.match(r"^[A-Z]+=", line_strip, re.IGNORECASE): 128 | parts = line_strip.split() 129 | for part in parts: 130 | if "=" in part: 131 | reg, val = part.split("=") 132 | registers[reg] = val 133 | 134 | # --- PARSE "kdump info" --- 135 | info_regex = re.compile(r"kdump info\s+(.*?)\s+kLLDB>", re.DOTALL) 136 | info_match = info_regex.search(clean_output) 137 | info_text = info_match.group(1).strip() if info_match else "" 138 | 139 | # --- PARSE "list kv_reset_tainted" --- 140 | snippet_regex = re.compile(r"list kv_reset_tainted\s+(.*?)\s+kLLDB>", re.DOTALL) 141 | snippet_match = snippet_regex.search(clean_output) 142 | source_snippet = snippet_match.group(1).strip() if snippet_match else "" 143 | 144 | # Debug prints 145 | print("\n=== PARSED RESULTS ===") 146 | mod_parsed = blame_module.group(1).strip() if blame_module else "" 147 | rip_parsed = rip_address.group(1).strip() if rip_address else "" 148 | func_parsed = blame_func.group(1).strip() if blame_func else "" 149 | print("Blame module:", mod_parsed) 150 | print("RIP address:", rip_parsed) 151 | print("Blame function:", func_parsed) 152 | print("Registers found:", registers) 153 | print("Info text:\n", info_text) 154 | print("Source snippet:\n", source_snippet) 155 | print("=====================") 156 | 157 | # Build analysis data 158 | analysis_data = { 159 | "module": mod_parsed, 160 | "rip": rip_parsed, 161 | "function": func_parsed, 162 | "registers": registers, 163 | "info": info_text, 164 | "source_code": source_snippet, 165 | } 166 | 167 | print("\nGenerating bug report with local LLM...") 168 | bug_report = generate_bug_report_locally(analysis_data, args.llm_path) 169 | 170 | final_report = "kLLDB sesion:\n" 171 | final_report += full_output 172 | final_report += "\n=====\n\n" 173 | final_report += "LLM report:\n" 174 | final_report += bug_report 175 | final_report += "\n=====\n\n" 176 | 177 | # Print or write to file, depending on --report-file 178 | if args.report_file: 179 | print(f"\nWriting bug report to {args.report_file} ...") 180 | with open(args.report_file, "w", encoding="utf-8") as rf: 181 | rf.write(final_report + "\n") 182 | print("Done.") 183 | else: 184 | print("\n=== Bug Report ===") 185 | print(final_report) 186 | print("==================") 187 | 188 | if __name__ == "__main__": 189 | main() 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kLLDB 2 | LLDB based debugger for Linux Kernel 3 | 4 | ## Install deps 5 | 6 | ``` 7 | echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list 8 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - 9 | sudo apt-get update 10 | sudo apt-get install -y llvm-19-dev clang-19 libclang-19-dev lld-19 pkg-config libgc-dev libssl-dev zlib1g-dev libcjson-dev libsqlite3-dev libunwind-dev 11 | sudo apt-get install -y python3.12-dev 12 | ``` 13 | 14 | Create symlink to `lldb-19`: 15 | 16 | ``` 17 | cd /usr/bin 18 | sudo ln -S ./lldb ../lib/llvm-19/bin/lldb 19 | ``` 20 | 21 | ## Build it 22 | 23 | ``` 24 | mkdir build && cd build 25 | cmake ../kLLDB/ -DCMAKE_BUILD_TYPE=Relase -DLLVM_DIR=/usr/lib/llvm-19/lib/cmake/llvm -GNinja 26 | ``` 27 | 28 | ## Examples 29 | 30 | `kLLDB` supports both live and offline debugging by parsing `kdump` coredump files. 31 | 32 | ### Offline debugging - kdump 33 | 34 | For the purpose of debugging a `kdump` file, we added an LLDB plugin, that is a wrapper around [libkdumpfile](https://github.com/ptesarik/libkdumpfile). 35 | Lets analyze a crash file from Linux kernel 5.15 and see some `kdump` specific options: 36 | 37 | ``` 38 | $ lldb 39 | (lldb) target create vmlinux 40 | Current executable set to '/path/to/KoviD/kovid/vmlinux' (x86_64). 41 | (lldb) plugin load /path/to/NewKLLDB/build/lib/libkLLDBOffline.so 42 | kLLDBOffline plugin loaded. 43 | kLLDB> kdump open ./crash.file 44 | Opened ./crash.file 45 | kLLDB> kdump info 46 | Crash File: ./crash.file 47 | Release: 5.15.0 48 | Arch: x86_64 49 | Crash Time: 2025-03-26 08:15:11 50 | BUILD-ID: 5854168ecc422202ede0880ac960351c37b57faa 51 | PAGESIZE: 4096 52 | Crash PID: 260 CPU0 53 | kLLDB> kdump load-lkm kovid.ko 54 | kovid.ko loaded at 0xffffffffc0000000 55 | kLLDB> kdump bugpoint 56 | Instructon pointer at rip=0xffffffffc0003dd5 57 | Blame function: kv_reset_tainted+5 58 | kLLDB> kdump source-dir-map /kovid/ /path/to/KoviD/kovid 59 | Mapped source directory '/kovid/' -> '/path/to/KoviD/kovid' 60 | kLLDB> list kv_reset_tainted 61 | File: /kovid/src/sys.c 62 | 1226 63 | 1227 if (!within_module(parent_ip, THIS_MODULE)) 64 | 1228 regs->ip = (unsigned long)hook->function; 65 | 1229 } 66 | 1230 67 | 1231 int kv_reset_tainted(unsigned long *tainted_ptr) 68 | 1232 { 69 | 1233 return test_and_clear_bit(TAINT_UNSIGNED_MODULE, tainted_ptr); 70 | 1234 } 71 | 1235 72 | 1236 #ifdef __x86_64__ 73 | 1237 #define _sys_arch(s) "__x64_" s 74 | 1238 #else 75 | 1239 #define _sys_arch(s) s 76 | kLLDB> kdump registers 77 | CPU: 0, PID: 260 78 | RAX=0x0000000000000000 RBX=0xFFFFFFFFC000C020 RCX=0x0000000000000000 RDX=0x0000000000000000 79 | RSI=0xFFFF88807FC17470 RDI=0x0000000000000000 RBP=0xFFFFFFFFC000EB60 RSP=0xFFFFC900000B3DC0 80 | R8=0xFFFFFFFF82741968 R9=0x00000000FFFFDFFF R10=0xFFFFFFFF82661980 R11=0xFFFFFFFF82661980 81 | R12=0xFFFF888005A98100 R13=0x000055C63C7FD45C R14=0x0000000000000003 R15=0x0000000000000000 82 | RIP=0xFFFFFFFFC0003DD5 CS=0x0010 EFLAGS=0x00000246 SS=0x0018 83 | ORIG_RAX=0xFFFFFFFFFFFFFFFF 84 | kLLDB> q 85 | ``` 86 | 87 | ### Live debugging 88 | 89 | In one terminal, run qemu as: 90 | 91 | ``` 92 | $ qemu-system-x86_64 -kernel "arch/x86/boot/bzImage" -append "root=/dev/sda rw console=ttyS0,115200 nokaslr init=/sbin/init" -drive format=raw,file=/path/to/rootfs.ext2 -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22,hostfwd=tcp::9999-:9999 -nographic -s -S 93 | ``` 94 | 95 | In second terminal, run the tool: 96 | 97 | ``` 98 | $ ./bin/kLLDB 99 | (lldb) command script import ./bin/kLLDB.py 100 | kLLDB plugin initialized successfully. 101 | kLLDB: Plugin loaded from /path/to/build/lib/libkLLDBLive.so 102 | kLLDB: Ready 103 | kLLDB> linux config /path/to/inux-5.15.15/vmlinux 104 | kLLDB: vmlinux path set to: /path/to/linux-5.15.15/vmlinux 105 | kLLDB> linux connect 106 | Failed to connect to GDB stub at 127.0.0.1:1234 107 | Process 1 stopped 108 | * thread #1, stop reason = signal SIGTRAP 109 | frame #0: 0x000000000000fff0 vmlinux`exception_stacks + 16368 110 | vmlinux`exception_stacks: 111 | -> 0xfff0 <+16368>: addb %al, (%rax) 112 | 0xfff2 <+16370>: addb %al, (%rax) 113 | 0xfff4 <+16372>: addb %al, (%rax) 114 | 0xfff6 <+16374>: addb %al, (%rax) 115 | kLLDB> linux continue 116 | Failed to continue the process. 117 | kLLDB> linux status 118 | Process 1 is running. 119 | kLLDB> linux stop 120 | Process interrupted (like 'process interrupt'). 121 | Process 1 stopped 122 | * thread #1, stop reason = signal SIGINT 123 | frame #0: 0xffffffff8102c57a vmlinux`amd_e400_idle [inlined] amd_e400_idle at process.c:780:3 124 | 777 */ 125 | 778 if (!boot_cpu_has_bug(X86_BUG_AMD_APIC_C1E)) { 126 | 779 default_idle(); 127 | -> 780 return; 128 | 781 } 129 | 782 130 | 783 tick_broadcast_enter(); 131 | kLLDB> bt 132 | * thread #1, stop reason = signal SIGINT 133 | * frame #0: 0xffffffff8102c57a vmlinux`amd_e400_idle [inlined] amd_e400_idle at process.c:780:3 134 | frame #1: 0xffffffff8102c56f vmlinux`amd_e400_idle at process.c:771:13 135 | frame #2: 0xffffffff81c221af vmlinux`default_idle_call at idle.c:112:3 136 | frame #3: 0xffffffff810a0f44 vmlinux`do_idle [inlined] cpuidle_idle_call at idle.c:194:3 137 | frame #4: 0xffffffff810a0eec vmlinux`do_idle at idle.c:306:4 138 | frame #5: 0xffffffff810a1149 vmlinux`cpu_startup_entry(state=CPUHP_ONLINE) at idle.c:403:3 139 | frame #6: 0xffffffff82d991d7 vmlinux`start_kernel at main.c:1144:2 140 | frame #7: 0xffffffff81000107 vmlinux`secondary_startup_64 at head_64.S:283 141 | kLLDB> q 142 | Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] n 143 | kLLDB> list 144 | 784 145 | 785 default_idle(); 146 | 786 147 | 787 /* 148 | 788 * The switch back from broadcast mode needs to be called with 149 | 789 * interrupts disabled. 150 | 790 */ 151 | kLLDB> q 152 | ``` 153 | 154 | ### Using LLM for bug report 155 | 156 | Download LLM: 157 | 158 | ``` 159 | $ wget https://huggingface.co/lmstudio-community/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf 160 | ``` 161 | 162 | Download packages: 163 | 164 | ``` 165 | $ python3 -m venv kdump-venv 166 | $ source kdump-venv/bin/activate 167 | $ pip3 install pexpect llama-cpp-python 168 | ``` 169 | 170 | There is a Python script that uses local LLM that parses `kLLDB` analysis for bug reporting. 171 | You can use it this way: 172 | 173 | ``` 174 | 175 | (kdump-venv) $ kLLDB/scripts/report-linux-bug.py --llm-path /path/to/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf --lldb-path /usr/bin/lldb --kdump-plugin-path /path/to/libkLLDBOffline.so --vmlinux-path vmlinux --crash-file-path ./crash.file --lkm-path /path/to/lkm.ko --source-dir-old /remote-dir/ --source-dir-new /path-to-local-dir/ --report-file=bug-report-32.txt 176 | $ cat bug-report-32.txt 177 | kLLDB sesion: 178 | (lldb) target create vmlinux 179 | Current executable set to '/path/to/KoviD/kovid/vmlinux' (x86_64). 180 | (lldb) plugin load /path/to/NewKLLDB/build/lib/libkLLDBOffline.so 181 | kLLDBOffline plugin loaded. 182 | kLLDB> kdump open ./crash.file 183 | Opened ./crash.file 184 | kLLDB> kdump info 185 | Crash File: ./crash.file 186 | Release: 5.15.0 187 | Arch: x86_64 188 | Crash Time: 2025-03-26 08:15:11 189 | BUILD-ID: 5854168ecc422202ede0880ac960351c37b57faa 190 | PAGESIZE: 4096 191 | Crash PID: 260 CPU0 192 | kLLDB> kdump load-lkm kovid.ko 193 | kovid.ko loaded at 0xffffffffc0000000 194 | kLLDB> kdump bugpoint 195 | Instructon pointer at rip=0xffffffffc0003dd5 196 | Blame function: kv_reset_tainted+5 197 | kLLDB> kdump source-dir-map /kovid/ /path/to/KoviD/kovid 198 | Mapped source directory '/kovid/' -> '/path/to/KoviD/kovid' 199 | kLLDB> list kv_reset_tainted 200 | File: /kovid/src/sys.c 201 | 1226 202 | 1227 if (!within_module(parent_ip, THIS_MODULE)) 203 | 1228 regs->ip = (unsigned long)hook->function; 204 | 1229 } 205 | 1230 206 | 1231 int kv_reset_tainted(unsigned long *tainted_ptr) 207 | 1232 { 208 | 1233 return test_and_clear_bit(TAINT_UNSIGNED_MODULE, tainted_ptr); 209 | 1234 } 210 | 1235 211 | 1236 #ifdef __x86_64__ 212 | 1237 #define _sys_arch(s) "__x64_" s 213 | 1238 #else 214 | 1239 #define _sys_arch(s) s 215 | kLLDB> kdump registers 216 | CPU: 0, PID: 260 217 | RAX=0x0000000000000000 RBX=0xFFFFFFFFC000C020 RCX=0x0000000000000000 RDX=0x0000000000000000 218 | RSI=0xFFFF88807FC17470 RDI=0x0000000000000000 RBP=0xFFFFFFFFC000EB60 RSP=0xFFFFC900000B3DC0 219 | R8=0xFFFFFFFF82741968 R9=0x00000000FFFFDFFF R10=0xFFFFFFFF82661980 R11=0xFFFFFFFF82661980 220 | R12=0xFFFF888005A98100 R13=0x000055C63C7FD45C R14=0x0000000000000003 R15=0x0000000000000000 221 | RIP=0xFFFFFFFFC0003DD5 CS=0x0010 EFLAGS=0x00000246 SS=0x0018 222 | ORIG_RAX=0xFFFFFFFFFFFFFFFF 223 | kLLDB> q 224 | Blame module: kovid.ko 225 | 226 | ===== 227 | 228 | LLM report: 229 | Alright, I'm trying to figure out why the Linux kernel is crashing with the given details. Let's start by looking at the information provided. 230 | 231 | The crash is happening in the `kv_reset_tainted` function, specifically at an instruction pointer `0xffffffffc0003dd5`. The function is part of the `kovid.ko` module. The register dump shows that `RIP` is pointing to this function, so the crash is definitely happening inside `kv_reset_tainted`. 232 | 233 | Looking at the function definition in `sys.c`, `kv_reset_tainted` takes a single argument `tainted_ptr`, which is a pointer to an unsigned long. The function uses `test_and_clear_bit` with `TAINT_UNSIGNED_MODULE` and `tainted_ptr` as arguments. 234 | 235 | In the register dump, the registers are in a state that might indicate an error. The `RAX` is zero, which might mean that the function isn't returning properly or there's an issue with the return value. The `RBX` has a value of `0xFFFFFFFFC000C020`, which is a 64-bit address, but I'm not sure if that's relevant here. 236 | 237 | I notice that the function is called with a pointer, but in the register dump, `RDI` (the first argument) is zero. That's odd because `RDI` is the destination for the first argument of a function. If `tainted_ptr` is being passed as zero, that could cause issues because the function expects a valid pointer. 238 | 239 | So, the likely cause is that `kv_reset_tainted` is being called with a null or invalid `tainted_ptr` argument. This might be due to a bug in the calling code that's not passing the correct pointer, or perhaps a null pointer dereference within the function itself. 240 | 241 | To debug this, I should check how `kv_reset_tainted` is being called. If it's being called with a null pointer, that's a problem. Also, I should look into the context where `tainted_ptr` is being set to ensure it's not null before calling the function. 242 | 243 | Another angle is to examine the `test_and_clear_bit` function to see if it's handling the `tainted_ptr` correctly, especially if it's not a valid pointer. Maybe there's a missing bounds check or a null pointer check that's causing the crash. 244 | 245 | In summary, the crash is due to a null or invalid pointer being passed to `kv_reset_tainted`, leading to an issue 246 | ===== 247 | ``` 248 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /plugins/kLLDBLive.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kLLDBLive.cpp by djolertrk 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // Convenient macro to ensure the symbol remains in the plugin. 20 | #define API __attribute__((used)) 21 | 22 | using namespace lldb; 23 | 24 | static std::string g_vmlinux_path = "vmlinux"; 25 | 26 | static std::vector g_module_search_paths; 27 | 28 | static bool FileExists(const std::string &path) { 29 | struct stat st; 30 | return (stat(path.c_str(), &st) == 0); 31 | } 32 | 33 | // Utility to make path absolute via realpath() if possible 34 | static std::string MakeAbsolutePath(const std::string &path) { 35 | char resolved[PATH_MAX]; 36 | if (realpath(path.c_str(), resolved)) { 37 | return std::string(resolved); 38 | } 39 | // If realpath fails (e.g. file doesn't exist yet), fallback: 40 | return path; 41 | } 42 | 43 | class LxSymbolsCommand : public SBCommandPluginInterface { 44 | public: 45 | bool DoExecute(SBDebugger debugger, char **command, 46 | SBCommandReturnObject &result) override { 47 | 48 | // 1) Collect all arguments as .ko file paths 49 | std::vector ko_paths; 50 | while (command && command[0]) { 51 | std::string arg = command[0]; 52 | command++; 53 | ko_paths.push_back(arg); 54 | } 55 | 56 | if (ko_paths.empty()) { 57 | result.AppendMessage("Usage: linux symbols /path/to/module.ko [...]\n"); 58 | result.SetStatus(eReturnStatusFailed); 59 | return false; 60 | } 61 | 62 | // 2) Create or re-create the main kernel target from g_vmlinux_path 63 | SBTarget target = debugger.GetSelectedTarget(); 64 | if (target.IsValid()) { 65 | // If already valid, remove it to get a clean slate 66 | debugger.DeleteTarget(target); 67 | } 68 | 69 | SBError error; 70 | target = debugger.CreateTarget(g_vmlinux_path.c_str(), nullptr, nullptr, 71 | false, error); 72 | if (!target.IsValid() || error.Fail()) { 73 | std::stringstream msg; 74 | msg << "Failed to create target from '" << g_vmlinux_path << "'\n" 75 | << "LLDB error: " << error.GetCString() << "\n"; 76 | result.AppendMessage(msg.str().c_str()); 77 | result.SetStatus(eReturnStatusFailed); 78 | return false; 79 | } 80 | 81 | // 3) For each .ko path, convert to absolute, then run "target modules add 82 | // " 83 | // This is the simplest approach to have LLDB parse the .ko as a module. 84 | for (auto &rel_path : ko_paths) { 85 | std::string abs_path = MakeAbsolutePath(rel_path); 86 | 87 | // Also check existence before passing to LLDB 88 | if (!FileExists(abs_path)) { 89 | std::stringstream msg; 90 | msg << "Skipping '" << rel_path << "': file not found.\n"; 91 | result.AppendMessage(msg.str().c_str()); 92 | continue; 93 | } 94 | 95 | std::string cmd = "target modules add \"" + abs_path + "\""; 96 | SBCommandReturnObject cmd_result; 97 | debugger.GetCommandInterpreter().HandleCommand(cmd.c_str(), cmd_result); 98 | 99 | if (cmd_result.GetStatus() != eReturnStatusSuccessFinishResult) { 100 | std::stringstream msg; 101 | msg << "Failed to add module '" << abs_path 102 | << "': " << cmd_result.GetError() << "\n"; 103 | result.AppendMessage(msg.str().c_str()); 104 | } else { 105 | std::stringstream msg; 106 | msg << "Added module: " << abs_path << "\n"; 107 | result.AppendMessage(msg.str().c_str()); 108 | } 109 | } 110 | 111 | // 4) Done 112 | result.AppendMessage("linux-symbols: All requested modules processed.\n"); 113 | result.SetStatus(eReturnStatusSuccessFinishResult); 114 | return true; 115 | } 116 | }; 117 | 118 | class LxConfigCommand : public SBCommandPluginInterface { 119 | public: 120 | bool DoExecute(SBDebugger debugger, char **command, 121 | SBCommandReturnObject &result) override { 122 | if (!command || !command[0]) { 123 | std::stringstream msg; 124 | msg << "Current vmlinux path: " << g_vmlinux_path << "\n"; 125 | msg << "Usage: linux config /path/to/vmlinux\n"; 126 | result.AppendMessage(msg.str().c_str()); 127 | result.SetStatus(eReturnStatusFailed); 128 | return false; 129 | } 130 | 131 | g_vmlinux_path = command[0]; 132 | 133 | std::stringstream msg; 134 | msg << "kLLDB: vmlinux path set to: " << g_vmlinux_path << "\n"; 135 | result.AppendMessage(msg.str().c_str()); 136 | result.SetStatus(eReturnStatusSuccessFinishResult); 137 | return true; 138 | } 139 | }; 140 | 141 | class LxConnectCommand : public SBCommandPluginInterface { 142 | public: 143 | bool DoExecute(SBDebugger debugger, char **command, 144 | SBCommandReturnObject &result) override { 145 | // Figure out the port spec 146 | // If user typed "linux connect :1234", prepend "127.0.0.1" 147 | // Default is "127.0.0.1:1234" 148 | std::string host_port = "127.0.0.1:1234"; 149 | if (command && command[0]) { 150 | host_port = command[0]; 151 | if (!host_port.empty() && host_port.front() == ':') { 152 | host_port = "127.0.0.1" + host_port; // => "127.0.0.1:1234" 153 | } 154 | } 155 | 156 | // Create an LLDB target object directly via the API 157 | SBError error; 158 | SBTarget target = debugger.CreateTarget(g_vmlinux_path.c_str(), // file 159 | nullptr, // triple 160 | nullptr, // platform 161 | false, // add_dependent_modules 162 | error); 163 | if (error.Fail() || !target.IsValid()) { 164 | std::stringstream msg; 165 | msg << "Failed to create target with '" << g_vmlinux_path << "'\n" 166 | << "LLDB error: " << error.GetCString(); 167 | result.AppendMessage(msg.str().c_str()); 168 | result.SetStatus(eReturnStatusFailed); 169 | return false; 170 | } 171 | 172 | // Connect to QEMU’s GDB stub via the process connect command 173 | std::string connect_cmd = "gdb-remote " + host_port; 174 | 175 | SBCommandReturnObject connect_result; 176 | debugger.GetCommandInterpreter().HandleCommand(connect_cmd.c_str(), 177 | connect_result); 178 | 179 | if (connect_result.GetStatus() != eReturnStatusSuccessFinishResult) { 180 | std::stringstream msg; 181 | msg << "Failed to connect to GDB stub at " << host_port << "\n" 182 | << connect_result.GetError(); 183 | result.AppendMessage(msg.str().c_str()); 184 | result.SetStatus(eReturnStatusFailed); 185 | return false; 186 | } 187 | 188 | // If all is well 189 | std::stringstream msg; 190 | msg << "kLLDB: Connected to GDB stub at " << host_port << "\n" 191 | << "Using vmlinux path: " << g_vmlinux_path << "\n"; 192 | result.AppendMessage(msg.str().c_str()); 193 | result.SetStatus(eReturnStatusSuccessFinishResult); 194 | return true; 195 | } 196 | }; 197 | 198 | // Command: linux linux stop 199 | class LxLinuxStopCommand : public lldb::SBCommandPluginInterface { 200 | public: 201 | bool DoExecute(lldb::SBDebugger debugger, char **command, 202 | lldb::SBCommandReturnObject &result) override { 203 | lldb::SBCommandReturnObject interrupt_result; 204 | debugger.GetCommandInterpreter().HandleCommand("process interrupt", 205 | interrupt_result); 206 | 207 | if (interrupt_result.GetStatus() == 208 | lldb::eReturnStatusSuccessFinishResult) { 209 | result.AppendMessage("Process interrupted (like 'process interrupt').\n"); 210 | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); 211 | } else { 212 | result.AppendMessage("Failed to interrupt the process.\n"); 213 | result.SetStatus(lldb::eReturnStatusFailed); 214 | } 215 | return true; 216 | } 217 | }; 218 | 219 | // Command: linux linux status 220 | class LxLinuxStatusCommand : public lldb::SBCommandPluginInterface { 221 | public: 222 | bool DoExecute(lldb::SBDebugger debugger, char **command, 223 | lldb::SBCommandReturnObject &result) override { 224 | lldb::SBCommandReturnObject status_result; 225 | debugger.GetCommandInterpreter().HandleCommand("process status", 226 | status_result); 227 | 228 | // Pass the status output to our caller 229 | result.AppendMessage(status_result.GetOutput()); 230 | // The sub-command's status is success if the underlying command succeeded 231 | if (status_result.GetStatus() == lldb::eReturnStatusSuccessFinishResult) { 232 | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); 233 | } else { 234 | result.SetStatus(lldb::eReturnStatusFailed); 235 | } 236 | return true; 237 | } 238 | }; 239 | 240 | class LxLinuxContinueCommand : public lldb::SBCommandPluginInterface { 241 | public: 242 | bool DoExecute(lldb::SBDebugger debugger, char **command, 243 | lldb::SBCommandReturnObject &result) override { 244 | // 1) Make sure we have a valid target 245 | SBTarget target = debugger.GetSelectedTarget(); 246 | if (!target.IsValid()) { 247 | result.AppendMessage("No valid target selected.\n"); 248 | result.SetStatus(lldb::eReturnStatusFailed); 249 | return false; 250 | } 251 | 252 | // 2) Make sure we have a valid process 253 | SBProcess process = target.GetProcess(); 254 | if (!process.IsValid()) { 255 | result.AppendMessage("No valid process. Must be connected.\n"); 256 | result.SetStatus(lldb::eReturnStatusFailed); 257 | return false; 258 | } 259 | 260 | // 3) Force synchronous mode if you want GDB-like blocking 261 | // Telling LLDB "run in sync mode" 262 | debugger.SetAsync(false); 263 | 264 | // 4) Actually continue via the SB API 265 | SBError err = process.Continue(); 266 | if (err.Fail()) { 267 | // 'Continue()' can fail if the process is not in a "stopped" state or if 268 | // there's an internal LLDB error. 269 | std::stringstream msg; 270 | msg << "Failed to continue the process: " << err.GetCString(); 271 | result.AppendMessage(msg.str().c_str()); 272 | result.SetStatus(lldb::eReturnStatusFailed); 273 | return false; 274 | } 275 | 276 | // If we get here, LLDB is blocking until the target stops again (breakpoint 277 | // or manual interrupt). 278 | // Once the target is stopped, we resume control in the plugin code: 279 | result.AppendMessage("Process continued synchronously.\n"); 280 | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); 281 | return true; 282 | } 283 | }; 284 | 285 | // 286 | // Plugin initialization entry point for LLDB 10+ 287 | // 288 | namespace lldb { 289 | bool PluginInitialize(SBDebugger debugger) { 290 | SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); 291 | debugger.SetPrompt("kLLDB> "); 292 | 293 | // Create a multiword command group named "linux" for Linux kernel helper 294 | // commands 295 | SBCommand linuxGroup = 296 | interpreter.AddMultiwordCommand("linux", "Linux kernel helper commands"); 297 | if (linuxGroup.IsValid()) { 298 | linuxGroup.AddCommand("symbols", new LxSymbolsCommand(), 299 | "Load Linux kernel and module symbols", nullptr); 300 | 301 | linuxGroup.AddCommand( 302 | "config", new LxConfigCommand(), 303 | "Set or show the path to vmlinux. Usage: linux config /path/to/vmlinux", 304 | nullptr); 305 | 306 | // Add subcommand: connect 307 | linuxGroup.AddCommand( 308 | "connect", new LxConnectCommand(), 309 | "Connect to a remote GDB server. Usage: linux connect :1234", nullptr); 310 | 311 | linuxGroup.AddCommand("stop", new LxLinuxStopCommand(), 312 | "Interrupt linux kernel process.", nullptr); 313 | 314 | linuxGroup.AddCommand("status", new LxLinuxStatusCommand(), 315 | "Show linux kernel process status).", nullptr); 316 | 317 | linuxGroup.AddCommand("continue", new LxLinuxContinueCommand(), 318 | "Continue linux kernel process execution.", nullptr); 319 | } 320 | 321 | // Inform the user that the plugin loaded successfully 322 | printf("kLLDB plugin initialized successfully.\n"); 323 | return true; 324 | } 325 | } // namespace lldb 326 | -------------------------------------------------------------------------------- /plugins/kLLDBOffline.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kLLDB - offline deubg of kdump crash files 3 | // by djolertrk 4 | // 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | struct X86_64_Regs { 48 | uint64_t r15, r14, r13, r12; 49 | uint64_t rbp, rbx, r11, r10; 50 | uint64_t r9, r8, rax, rcx; 51 | uint64_t rdx, rsi, rdi, orig_rax; 52 | uint64_t rip, cs, eflags, rsp, ss; 53 | 54 | X86_64_Regs() { ::memset(this, 0, sizeof(*this)); } 55 | }; 56 | 57 | struct ThreadInfo { 58 | uint64_t tid; // from "cpu.#.pid" 59 | X86_64_Regs regs; 60 | 61 | std::string comm; // from "cpu.#.comm" (if available) 62 | uint64_t task; // from "cpu.#.task" (if available) 63 | uint64_t thread_info; // from "cpu.#.thread_info" (if available) 64 | uint64_t cpu_index; // to keep track of CPU # (optional) 65 | }; 66 | 67 | // TODO: For targets other than x86_64, this needs to be fixed. 68 | #define ELF_NGREG 27 69 | struct timeval_64 { 70 | int64_t tv_sec; 71 | int64_t tv_usec; 72 | } __attribute__((packed)); 73 | 74 | struct elf_prstatus { 75 | struct { 76 | int32_t si_signo; 77 | int32_t si_code; 78 | int32_t si_errno; 79 | } __attribute__((packed)) pr_info; 80 | int16_t pr_cursig; 81 | char _pad1[2]; 82 | uint64_t pr_sigpend; 83 | uint64_t pr_sighold; 84 | int32_t pr_pid; 85 | int32_t pr_ppid; 86 | int32_t pr_pgrp; 87 | int32_t pr_sid; 88 | struct timeval_64 pr_utime; 89 | struct timeval_64 pr_stime; 90 | struct timeval_64 pr_cutime; 91 | struct timeval_64 pr_cstime; 92 | uint64_t pr_reg[ELF_NGREG]; 93 | } __attribute__((packed)); 94 | 95 | struct _kdump_blob { 96 | /** Reference counter. */ 97 | unsigned long refcnt; 98 | 99 | /** Pin counter. */ 100 | unsigned long pincnt; 101 | 102 | void *data; /**< Binary data. */ 103 | size_t size; /**< Size of binary data. */ 104 | }; 105 | 106 | //-------------------------------------------------------------------------------------- 107 | // KdumpBackend: uses libkdumpfile for reading a kdump 108 | //-------------------------------------------------------------------------------------- 109 | 110 | class KdumpBackend { 111 | public: 112 | KdumpBackend() : m_ctx(nullptr), m_is_open(false) {} 113 | 114 | ~KdumpBackend() { Close(); } 115 | 116 | bool Open(const std::string &path) { 117 | Close(); 118 | 119 | m_ctx = kdump_new(); 120 | if (!m_ctx) { 121 | m_err = "kdump_new() failed"; 122 | return false; 123 | } 124 | 125 | int fd = ::open(path.c_str(), O_RDONLY); 126 | if (fd < 0) { 127 | m_err = "Cannot open file: " + path; 128 | kdump_free(m_ctx); 129 | m_ctx = nullptr; 130 | return false; 131 | } 132 | 133 | kdump_status st = kdump_open_fd(m_ctx, fd); 134 | ::close(fd); 135 | if (st != KDUMP_OK) { 136 | m_err = std::string("kdump_open_fd() failed: ") + kdump_get_err(m_ctx); 137 | kdump_free(m_ctx); 138 | m_ctx = nullptr; 139 | return false; 140 | } 141 | 142 | ParseCpus(); 143 | m_is_open = true; 144 | return true; 145 | } 146 | 147 | void Close() { 148 | if (m_ctx) { 149 | kdump_free(m_ctx); 150 | m_ctx = nullptr; 151 | } 152 | m_threads.clear(); 153 | m_is_open = false; 154 | } 155 | 156 | bool IsOpen() const { return m_is_open; } 157 | 158 | std::string GetError() const { return m_err; } 159 | 160 | const std::vector &GetThreads() const { return m_threads; } 161 | 162 | // A minimal read from kernel VA 163 | bool ReadMemory(uint64_t va, void *buf, size_t size) { 164 | if (!m_ctx) { 165 | return false; 166 | } 167 | 168 | uint8_t *dst = static_cast(buf); 169 | size_t done = 0; 170 | while (done < size) { 171 | size_t chunk = size - done; 172 | size_t got = chunk; 173 | kdump_status s = kdump_read(m_ctx, KDUMP_KVADDR, 174 | (kdump_addr_t)(va + done), dst + done, &got); 175 | if (s != KDUMP_OK || got == 0) { 176 | // zero fill remainder 177 | ::memset(dst + done, 0, chunk); 178 | return false; 179 | } 180 | done += got; 181 | } 182 | return true; 183 | } 184 | 185 | // read a top-level attribute 186 | std::string GetAttrString(const std::string &path) { 187 | if (!m_ctx) 188 | return ""; 189 | kdump_attr_ref_t ref; 190 | if (kdump_attr_ref(m_ctx, path.c_str(), &ref) != KDUMP_OK) 191 | return ""; 192 | 193 | kdump_attr_t a; 194 | if (kdump_attr_ref_get(m_ctx, &ref, &a) != KDUMP_OK) { 195 | kdump_attr_unref(m_ctx, &ref); 196 | return ""; 197 | } 198 | 199 | std::string result; 200 | if (a.type == KDUMP_STRING && a.val.string) { 201 | result = a.val.string; 202 | } else if (a.type == KDUMP_NUMBER) { 203 | char tmp[64]; 204 | ::snprintf(tmp, sizeof(tmp), "%" PRIu64, a.val.number); 205 | result = tmp; 206 | } 207 | 208 | kdump_attr_discard(m_ctx, &a); 209 | kdump_attr_unref(m_ctx, &ref); 210 | return result; 211 | } 212 | 213 | private: 214 | // Read process/thread data from "cpu". 215 | void ParseCpus() { 216 | if (!m_ctx) 217 | return; 218 | 219 | // Get a reference to the "cpu" attribute 220 | kdump_attr_ref_t cpuRef; 221 | if (kdump_attr_ref(m_ctx, "cpu", &cpuRef) != KDUMP_OK) 222 | return; 223 | 224 | // Start iteration over all cpu. subkeys 225 | kdump_attr_iter_t it; 226 | if (kdump_attr_ref_iter_start(m_ctx, &cpuRef, &it) != KDUMP_OK) { 227 | kdump_attr_unref(m_ctx, &cpuRef); 228 | return; 229 | } 230 | 231 | while (it.key) { 232 | ThreadInfo thr; 233 | std::memset(&thr, 0, sizeof(thr)); 234 | 235 | // (1) CPU index from "cpu." 236 | thr.cpu_index = strtoull(it.key, nullptr, 10); // e.g. "0" 237 | 238 | // (2) TID from "cpu.#.pid" 239 | { 240 | std::string ppath = std::string("cpu.") + it.key + ".pid"; 241 | kdump_attr_ref_t pRef; 242 | if (kdump_attr_ref(m_ctx, ppath.c_str(), &pRef) == KDUMP_OK) { 243 | kdump_attr_t a; 244 | if (kdump_attr_ref_get(m_ctx, &pRef, &a) == KDUMP_OK) { 245 | if (a.type == KDUMP_NUMBER) { 246 | thr.tid = a.val.number; 247 | } 248 | kdump_attr_discard(m_ctx, &a); 249 | } 250 | kdump_attr_unref(m_ctx, &pRef); 251 | } 252 | } 253 | 254 | // We can do: 255 | // thr.regs.rip = ReadReg(it.key, "rip"); 256 | // thr.regs.rbp = ReadReg(it.key, "rbp"); 257 | // etc. but will do prstatus parse. 258 | 259 | // Now parse PRSTATUS for the *full* register set, if present 260 | { 261 | std::string prPath = std::string("cpu.") + it.key + ".PRSTATUS"; 262 | kdump_attr_ref_t prRef; 263 | if (kdump_attr_ref(m_ctx, prPath.c_str(), &prRef) == KDUMP_OK) { 264 | kdump_attr_t prAtt; 265 | if (kdump_attr_ref_get(m_ctx, &prRef, &prAtt) == KDUMP_OK) { 266 | // Check for KDUMP_BLOB type, then the size 267 | if (prAtt.type == KDUMP_BLOB && prAtt.val.blob) { 268 | size_t sz = prAtt.val.blob->size; 269 | if (sz >= sizeof(elf_prstatus)) { 270 | elf_prstatus prs; 271 | std::memcpy(&prs, prAtt.val.blob->data, sizeof(prs)); 272 | 273 | // Overwrite tid from pr_pid 274 | thr.tid = prs.pr_pid; 275 | 276 | // x86_64 pr_reg[]: 0=R15,1=R14,2=R13,3=R12,4=RBP,5=RBX,... 277 | thr.regs.r15 = prs.pr_reg[0]; 278 | thr.regs.r14 = prs.pr_reg[1]; 279 | thr.regs.r13 = prs.pr_reg[2]; 280 | thr.regs.r12 = prs.pr_reg[3]; 281 | thr.regs.rbp = prs.pr_reg[4]; 282 | thr.regs.rbx = prs.pr_reg[5]; 283 | thr.regs.r11 = prs.pr_reg[6]; 284 | thr.regs.r10 = prs.pr_reg[7]; 285 | thr.regs.r9 = prs.pr_reg[8]; 286 | thr.regs.r8 = prs.pr_reg[9]; 287 | thr.regs.rax = prs.pr_reg[10]; 288 | thr.regs.rcx = prs.pr_reg[11]; 289 | thr.regs.rdx = prs.pr_reg[12]; 290 | thr.regs.rsi = prs.pr_reg[13]; 291 | thr.regs.rdi = prs.pr_reg[14]; 292 | thr.regs.orig_rax = prs.pr_reg[15]; 293 | thr.regs.rip = prs.pr_reg[16]; 294 | thr.regs.cs = prs.pr_reg[17]; 295 | thr.regs.eflags = prs.pr_reg[18]; 296 | thr.regs.rsp = prs.pr_reg[19]; 297 | thr.regs.ss = prs.pr_reg[20]; 298 | // TODO: parse fs_base, gs_base, ds, es, fs, gs etc. 299 | } 300 | } 301 | kdump_attr_discard(m_ctx, &prAtt); 302 | } 303 | kdump_attr_unref(m_ctx, &prRef); 304 | } 305 | } 306 | 307 | // push the thread in the vector 308 | m_threads.push_back(thr); 309 | 310 | // iterate next CPU 311 | if (kdump_attr_iter_next(m_ctx, &it) != KDUMP_OK) { 312 | break; 313 | } 314 | } 315 | 316 | kdump_attr_iter_end(m_ctx, &it); 317 | kdump_attr_unref(m_ctx, &cpuRef); 318 | } 319 | 320 | uint64_t ReadReg(const std::string &cpuKey, const std::string ®Name) { 321 | std::string path = "cpu." + cpuKey + ".reg." + regName; 322 | kdump_attr_ref_t ref; 323 | if (kdump_attr_ref(m_ctx, path.c_str(), &ref) != KDUMP_OK) { 324 | return 0; 325 | } 326 | kdump_attr_t a; 327 | if (kdump_attr_ref_get(m_ctx, &ref, &a) != KDUMP_OK) { 328 | kdump_attr_unref(m_ctx, &ref); 329 | return 0; 330 | } 331 | uint64_t val = 0; 332 | if (a.type == KDUMP_NUMBER) { 333 | val = a.val.number; 334 | } 335 | kdump_attr_discard(m_ctx, &a); 336 | kdump_attr_unref(m_ctx, &ref); 337 | return val; 338 | } 339 | 340 | private: 341 | kdump_ctx_t *m_ctx; 342 | bool m_is_open; 343 | std::string m_err; 344 | std::vector m_threads; 345 | }; 346 | 347 | static KdumpBackend g_kdump; 348 | static std::string g_coreFilePath; 349 | 350 | static bool SafeReadU64(uint64_t va, uint64_t &val) { 351 | val = 0; 352 | uint8_t tmp[8]; 353 | if (!g_kdump.ReadMemory(va, tmp, sizeof(tmp))) { 354 | return false; 355 | } 356 | val = *(const uint64_t *)(tmp); 357 | return true; 358 | } 359 | 360 | struct Frame { 361 | uint64_t rip; 362 | uint64_t rbp; 363 | }; 364 | 365 | static std::vector WalkStack_RBP(uint64_t rip, uint64_t rbp) { 366 | std::vector frames; 367 | frames.reserve(64); 368 | 369 | frames.push_back({rip, rbp}); 370 | 371 | for (int i = 0; i < 63; i++) { 372 | uint64_t nrip = 0, nrbp = 0; 373 | if (!SafeReadU64(rbp, nrbp)) 374 | break; 375 | if (!SafeReadU64(rbp + 8, nrip)) 376 | break; 377 | if (nrbp <= rbp) 378 | break; 379 | if (nrip < 0xffffffff80000000ULL) 380 | break; 381 | 382 | frames.push_back({nrip, nrbp}); 383 | rbp = nrbp; 384 | } 385 | return frames; 386 | } 387 | 388 | //-------------------------------------------------------------------------------------- 389 | // Commands 390 | //-------------------------------------------------------------------------------------- 391 | 392 | // TODO: Check Linux 6.x 393 | class CoreLoadLKMCommand : public lldb::SBCommandPluginInterface { 394 | public: 395 | bool DoExecute(lldb::SBDebugger dbg, char **cmd, 396 | lldb::SBCommandReturnObject &res) override { 397 | // Check if we have a valid dump file loaded 398 | if (!g_kdump.IsOpen()) { 399 | res.AppendMessage("No open kdump. 'kdump open ' first."); 400 | return false; 401 | } 402 | 403 | // Ensure the user has provided the .ko path 404 | if (!cmd || !cmd[0]) { 405 | res.AppendMessage("Usage: kdump load-lkm "); 406 | return false; 407 | } 408 | 409 | // Retrieve the kernel release from the kdump. (e.g. 410 | // "5.15.0-051500-generic") 411 | std::string release = g_kdump.GetAttrString("linux.uts.release"); 412 | if (release.empty()) { 413 | res.AppendMessage( 414 | "Warning: kernel version (linux.uts.release) not found. " 415 | "The default LKM load address may be incorrect!"); 416 | } else { 417 | // Simple check: if "5.15." is not present, warn the user 418 | if (release.find("5.15.") == std::string::npos) { 419 | std::ostringstream warn; 420 | warn << "Warning: kernel version is '" << release 421 | << "', which is NOT 5.15.x. The default LKM load address " 422 | "(0xffffffffc0000000) may be incorrect!"; 423 | res.AppendMessage(warn.str().c_str()); 424 | } 425 | } 426 | 427 | static bool lkm_loaded = false; 428 | if (lkm_loaded) { 429 | // Already loaded once this session 430 | res.AppendMessage("LKM already loaded, ignoring."); 431 | return true; 432 | } 433 | 434 | std::string lkmPath = cmd[0]; 435 | 436 | // Ensure we have a valid target (the user presumably has done "target 437 | // create vmlinux") 438 | lldb::SBTarget target = dbg.GetSelectedTarget(); 439 | if (!target.IsValid()) { 440 | res.AppendMessage( 441 | "No valid target. Use 'target create vmlinux' or 'kdump open'."); 442 | return false; 443 | } 444 | 445 | // Actually load the module at slide 0xffffffffc0000000 446 | // 1) Add the image 447 | { 448 | std::ostringstream oss; 449 | oss << "image add " << lkmPath; 450 | dbg.HandleCommand(oss.str().c_str()); 451 | } 452 | 453 | // 2) Then load with --slide 454 | { 455 | std::ostringstream oss; 456 | oss << "image load --file " << lkmPath << " --slide 0xffffffffc0000000"; 457 | dbg.HandleCommand(oss.str().c_str()); 458 | } 459 | 460 | // Mark as loaded 461 | lkm_loaded = true; 462 | 463 | // Success message 464 | std::ostringstream msg; 465 | msg << lkmPath << " loaded at 0xffffffffc0000000"; 466 | res.AppendMessage(msg.str().c_str()); 467 | 468 | return true; 469 | } 470 | }; 471 | 472 | class CoreSourceDirMapCommand : public lldb::SBCommandPluginInterface { 473 | public: 474 | bool DoExecute(lldb::SBDebugger dbg, char **cmd, 475 | lldb::SBCommandReturnObject &res) override { 476 | // Basic usage check 477 | if (!cmd || !cmd[0] || !cmd[1]) { 478 | res.AppendMessage("Usage: kdump source-dir-map "); 479 | return false; 480 | } 481 | 482 | std::string fromDir = cmd[0]; 483 | std::string toDir = cmd[1]; 484 | 485 | // We assume a valid target is open, but it's not strictly required to do 486 | // source mapping. 487 | lldb::SBTarget target = dbg.GetSelectedTarget(); 488 | if (!target.IsValid()) { 489 | res.AppendMessage("Warning: No valid target. Mapping anyway."); 490 | } 491 | 492 | // Build and execute the actual LLDB command: 493 | // settings set target.source-map 494 | std::ostringstream oss; 495 | oss << "settings set target.source-map " << fromDir << " " << toDir; 496 | 497 | dbg.HandleCommand(oss.str().c_str()); 498 | 499 | // Confirm success 500 | std::ostringstream msg; 501 | msg << "Mapped source directory '" << fromDir << "' -> '" << toDir << "'"; 502 | res.AppendMessage(msg.str().c_str()); 503 | 504 | return true; 505 | } 506 | }; 507 | 508 | // TODO: Fix this for other kernels e.g. 6.x 509 | static constexpr uint64_t MODULE_BASE = 0xffffffffc0000000; 510 | 511 | // Helper to find the "best" symbol for an offset. 512 | // Returns (symbolName, offsetInSymbol). 513 | static std::pair 514 | FindSymbolInModule(llvm::StringRef koPath, uint64_t moduleOffset) { 515 | namespace obj = llvm::object; 516 | 517 | // Prepare return values (if fail, return something plausible) 518 | std::string bestName = ""; 519 | uint64_t offsetIntoSym = moduleOffset; 520 | 521 | // Load the .ko file into memory 522 | auto binOrErr = obj::createBinary(koPath); 523 | if (!binOrErr) { 524 | // In real code, you should log or print 525 | // llvm::toString(binOrErr.takeError()) 526 | return {bestName, offsetIntoSym}; 527 | } 528 | 529 | // We expect a valid object file 530 | obj::Binary &bin = *binOrErr.get().getBinary(); 531 | auto *objFile = llvm::dyn_cast(&bin); 532 | if (!objFile) { 533 | return {bestName, offsetIntoSym}; 534 | } 535 | 536 | // We will find the symbol whose "Value" is <= moduleOffset, 537 | // and is the largest such Value (i.e. the nearest preceding symbol). 538 | // E.g. if offset is 0x3dd5, we want the symbol that starts at or before 539 | // 0x3dd5. 540 | uint64_t bestSymAddr = 0; 541 | for (auto sym : objFile->symbols()) { 542 | // Symbol must be in the .text or other relevant section (STT_FUNC). 543 | // We can check type or flags if desired: 544 | auto symType = sym.getType(); 545 | if (!symType) // if error 546 | continue; 547 | if (*symType != obj::SymbolRef::ST_Function && 548 | *symType != obj::SymbolRef::ST_Data && 549 | *symType != obj::SymbolRef::ST_Unknown) { 550 | // For kernel modules, sometimes STT_NOTYPE is used; we can relax checks 551 | continue; 552 | } 553 | 554 | // Get symbol address (Value) 555 | llvm::Expected addrOrErr = sym.getValue(); 556 | if (!addrOrErr) 557 | continue; 558 | uint64_t symAddr = *addrOrErr; // offset within the module file 559 | 560 | // If symbol name is not available, skip 561 | llvm::Expected nameOrErr = sym.getName(); 562 | if (!nameOrErr) 563 | continue; 564 | llvm::StringRef symName = *nameOrErr; 565 | if (symName.empty()) 566 | continue; 567 | 568 | // We want the symbol that starts <= moduleOffset, but is closest 569 | if (symAddr <= moduleOffset && symAddr >= bestSymAddr) { 570 | bestSymAddr = symAddr; 571 | bestName = symName.str(); 572 | } 573 | } 574 | 575 | // The offset within that symbol is (moduleOffset - bestSymAddr) 576 | offsetIntoSym = moduleOffset - bestSymAddr; 577 | return {bestName, offsetIntoSym}; 578 | } 579 | 580 | /// A helper to get the path from an SBFileSpec in a portable way 581 | static std::string GetFileSpecPath(const lldb::SBFileSpec &fspec) { 582 | if (!fspec.IsValid()) 583 | return std::string(); 584 | char path_buf[1024]; 585 | uint32_t len = fspec.GetPath(path_buf, sizeof(path_buf)); 586 | if (len == 0) 587 | return std::string(); 588 | return std::string(path_buf); 589 | } 590 | 591 | static bool FindModuleForAddress(lldb::SBTarget &target, uint64_t rip, 592 | std::string &outModulePath) { 593 | uint32_t numMods = target.GetNumModules(); 594 | for (uint32_t i = 0; i < numMods; ++i) { 595 | lldb::SBModule mod = target.GetModuleAtIndex(i); 596 | if (!mod.IsValid()) 597 | continue; 598 | 599 | uint32_t numSecs = mod.GetNumSections(); 600 | for (uint32_t s = 0; s < numSecs; ++s) { 601 | lldb::SBSection sec = mod.GetSectionAtIndex(s); 602 | if (!sec.IsValid()) 603 | continue; 604 | 605 | // The load address is fetched from the section object, not from the 606 | // target 607 | uint64_t secLoadAddr = sec.GetLoadAddress(target); 608 | uint64_t secSize = sec.GetByteSize(); 609 | if (secLoadAddr == LLDB_INVALID_ADDRESS) 610 | continue; 611 | 612 | // Check if rip lies within [secLoadAddr, secLoadAddr+secSize) 613 | if (rip >= secLoadAddr && rip < (secLoadAddr + secSize)) { 614 | lldb::SBFileSpec fspec = mod.GetFileSpec(); 615 | std::string dir(fspec.GetDirectory()); 616 | std::string fname(fspec.GetFilename()); 617 | if (!dir.empty()) { 618 | outModulePath = dir + "/" + fname; 619 | } else { 620 | outModulePath = fname; 621 | } 622 | return true; 623 | } 624 | } 625 | } 626 | return false; // not found 627 | } 628 | 629 | class CoreBugpointCommand : public lldb::SBCommandPluginInterface { 630 | public: 631 | bool DoExecute(lldb::SBDebugger dbg, char **cmd, 632 | lldb::SBCommandReturnObject &res) override { 633 | // Ensure we have a valid kdump 634 | if (!g_kdump.IsOpen()) { 635 | res.AppendMessage("No open kdump. 'kdump open ' first."); 636 | return false; 637 | } 638 | 639 | lldb::SBTarget target = dbg.GetSelectedTarget(); 640 | if (!target.IsValid()) { 641 | res.AppendMessage("No valid target - 'target create vmlinux' maybe?"); 642 | return false; 643 | } 644 | 645 | // Pick CPU #0 / first thread 646 | auto &threads = g_kdump.GetThreads(); 647 | if (threads.empty()) { 648 | res.AppendMessage("No threads in dump!"); 649 | return false; 650 | } 651 | 652 | const auto &r = threads[0].regs; 653 | uint64_t rip = r.rip; 654 | uint64_t rbp = r.rbp; 655 | 656 | // Perform naive RBP-based backtrace 657 | auto frames = WalkStack_RBP(rip, rbp); 658 | 659 | // Now for each frame, we compute offset from module base 660 | // and parse the .ko to find the function symbol 661 | for (size_t i = 0; i < frames.size(); i++) { 662 | // Print the raw RIP/RBP 663 | char line[256]; 664 | // ::snprintf(line, sizeof(line), 665 | // "#%zu rip=0x%016" PRIx64 " rbp=0x%016" PRIx64, 666 | // i, frames[i].rip, frames[i].rbp); 667 | ::snprintf(line, sizeof(line), 668 | "Instructon pointer at rip=0x%016" PRIx64 "", frames[i].rip); 669 | res.AppendMessage(line); 670 | 671 | std::string blameModulePath; 672 | uint64_t offset = frames[i].rip; 673 | if (!FindModuleForAddress(target, offset, blameModulePath)) { 674 | char err_line[128]; 675 | ::snprintf(err_line, sizeof(err_line), 676 | "Unable to find loaded module with address 0x%016" PRIx64 "", 677 | frames[i].rip); 678 | res.AppendMessage(err_line); 679 | return false; 680 | } 681 | 682 | std::ostringstream oss; 683 | if (!blameModulePath.empty()) { 684 | llvm::outs() << "Blame module: " << blameModulePath << "\n"; 685 | res.AppendMessage(oss.str().c_str()); 686 | } 687 | 688 | // If the RIP is in range for our lkm ko, let's parse that .ko file 689 | // This is just a check if rip >= MODULE_BASE, < MODULE_BASE + 690 | // (size?) Let's do a simple check if it's >= base (and < base+someMax). 691 | // For a real check, you might parse sections to see actual .text size. 692 | // Otherwise we assume the address is good - from vmlinux. 693 | if (frames[i].rip >= MODULE_BASE && 694 | frames[i].rip < (MODULE_BASE + 0x100000)) { 695 | offset = frames[i].rip - MODULE_BASE; 696 | } 697 | 698 | // Manually parse the .ko symbol table 699 | auto sym = FindSymbolInModule(blameModulePath, offset); 700 | if (!sym.first.empty()) { 701 | // If we found a symbol, print it 702 | oss << "Blame function: " << sym.first; 703 | if (sym.second) { 704 | oss << "+" << sym.second; 705 | } 706 | res.AppendMessage(oss.str().c_str()); 707 | } 708 | } 709 | 710 | return true; 711 | } 712 | }; 713 | 714 | class CoreInfoCommand : public lldb::SBCommandPluginInterface { 715 | public: 716 | bool DoExecute(lldb::SBDebugger dbg, char **cmd, 717 | lldb::SBCommandReturnObject &res) override { 718 | if (!g_kdump.IsOpen()) { 719 | res.AppendMessage("No open kdump for 'info'"); 720 | return false; 721 | } 722 | // example: read some stuff 723 | std::string release = g_kdump.GetAttrString("linux.uts.release"); 724 | std::string arch = g_kdump.GetAttrString("arch.name"); 725 | std::string ctime = 726 | g_kdump.GetAttrString("linux.vmcoreinfo.lines.CRASHTIME"); 727 | std::string buildid = 728 | g_kdump.GetAttrString("linux.vmcoreinfo.lines.BUILD-ID"); 729 | std::string pagesize = 730 | g_kdump.GetAttrString("linux.vmcoreinfo.lines.PAGESIZE"); 731 | 732 | std::string timestr = "(none)"; 733 | if (!ctime.empty()) { 734 | unsigned long long epoch = 0ULL; 735 | ::sscanf(ctime.c_str(), "%llu", &epoch); 736 | if (epoch != 0ULL) { 737 | time_t rawt = (time_t)epoch; 738 | struct tm tmpbuf; 739 | if (localtime_r(&rawt, &tmpbuf)) { 740 | char tbuf[64]; 741 | ::strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tmpbuf); 742 | timestr = tbuf; 743 | } 744 | } 745 | } 746 | 747 | std::ostringstream oss; 748 | oss << " Crash File: " << g_coreFilePath << "\n" 749 | << " Release: " << release << "\n" 750 | << " Arch: " << arch << "\n" 751 | << " Crash Time: " << timestr << "\n" 752 | << " BUILD-ID: " << buildid << "\n" 753 | << " PAGESIZE: " << pagesize << "\n"; 754 | 755 | auto &threads = g_kdump.GetThreads(); 756 | for (size_t i = 0; i < threads.size(); i++) { 757 | const ThreadInfo &th = threads[i]; 758 | // skip PID == 0. 759 | if (th.tid == 0) 760 | continue; 761 | 762 | oss << " Crash PID: " << th.tid << " CPU" << th.cpu_index << '\n'; 763 | } 764 | 765 | res.AppendMessage(oss.str().c_str()); 766 | return true; 767 | } 768 | }; 769 | 770 | class CoreRegistersCommand : public lldb::SBCommandPluginInterface { 771 | public: 772 | bool DoExecute(lldb::SBDebugger dbg, char **cmd, 773 | lldb::SBCommandReturnObject &res) override { 774 | if (!g_kdump.IsOpen()) { 775 | res.AppendMessage("No open kdump for 'info'"); 776 | return false; 777 | } 778 | 779 | std::ostringstream oss; 780 | auto &threads = g_kdump.GetThreads(); 781 | for (size_t i = 0; i < threads.size(); i++) { 782 | const ThreadInfo &th = threads[i]; 783 | // skip PID == 0. 784 | if (th.tid == 0) 785 | continue; 786 | // Print a header line about the thread/cpu 787 | oss << " CPU: " << th.cpu_index << ", PID: " << th.tid << "\n"; 788 | 789 | // Switch to hex, uppercase, with zero padding if desired 790 | oss << std::uppercase << std::hex << std::setfill('0'); 791 | 792 | // First line: RAX, RBX, RCX, RDX 793 | oss << " RAX=0x" << std::setw(16) << th.regs.rax << " RBX=0x" 794 | << std::setw(16) << th.regs.rbx << " RCX=0x" << std::setw(16) 795 | << th.regs.rcx << " RDX=0x" << std::setw(16) << th.regs.rdx << "\n"; 796 | 797 | // Second line: RSI, RDI, RBP, RSP 798 | oss << " RSI=0x" << std::setw(16) << th.regs.rsi << " RDI=0x" 799 | << std::setw(16) << th.regs.rdi << " RBP=0x" << std::setw(16) 800 | << th.regs.rbp << " RSP=0x" << std::setw(16) << th.regs.rsp << "\n"; 801 | 802 | // Third line: R8, R9, R10, R11 803 | oss << " R8=0x" << std::setw(16) << th.regs.r8 << " R9=0x" 804 | << std::setw(16) << th.regs.r9 << " R10=0x" << std::setw(16) 805 | << th.regs.r10 << " R11=0x" << std::setw(16) << th.regs.r11 << "\n"; 806 | 807 | // Fourth line: R12, R13, R14, R15 808 | oss << " R12=0x" << std::setw(16) << th.regs.r12 << " R13=0x" 809 | << std::setw(16) << th.regs.r13 << " R14=0x" << std::setw(16) 810 | << th.regs.r14 << " R15=0x" << std::setw(16) << th.regs.r15 << "\n"; 811 | 812 | // Next line: RIP, CS, EFLAGS, SS, ORIG_RAX 813 | // (cs and ss are 16-bit, eflags might be 32 bits, but we can show them in 814 | // hex) 815 | oss << " RIP=0x" << std::setw(16) << th.regs.rip << " CS=0x" 816 | << std::setw(4) << (uint16_t)th.regs.cs << " EFLAGS=0x" 817 | << std::setw(8) << (uint32_t)th.regs.eflags << " SS=0x" 818 | << std::setw(4) << (uint16_t)th.regs.ss << "\n"; 819 | 820 | oss << " ORIG_RAX=0x" << std::setw(16) << th.regs.orig_rax << "\n\n"; 821 | 822 | // restore default formatting 823 | oss << std::dec << std::nouppercase; 824 | } 825 | 826 | res.AppendMessage(oss.str().c_str()); 827 | return true; 828 | } 829 | }; 830 | 831 | class CoreOpenCommand : public lldb::SBCommandPluginInterface { 832 | public: 833 | bool DoExecute(lldb::SBDebugger dbg, char **cmd, 834 | lldb::SBCommandReturnObject &res) override { 835 | if (!cmd || !cmd[0]) { 836 | res.AppendMessage("Usage: kdump open "); 837 | return false; 838 | } 839 | 840 | std::string vmcorePath = cmd[0]; 841 | std::string vmlinuxPath; 842 | if (cmd[1]) { 843 | vmlinuxPath = cmd[1]; 844 | } 845 | 846 | // close old 847 | g_kdump.Close(); 848 | g_coreFilePath = vmcorePath; 849 | 850 | if (!g_kdump.Open(vmcorePath)) { 851 | std::string e = "Failed to open: "; 852 | e += vmcorePath + " => " + g_kdump.GetError(); 853 | res.AppendMessage(e.c_str()); 854 | return false; 855 | } 856 | 857 | lldb::SBTarget target = dbg.GetSelectedTarget(); 858 | if (!vmlinuxPath.empty()) { 859 | lldb::SBError err; 860 | target = dbg.CreateTarget(vmlinuxPath.c_str(), "", "", true, err); 861 | if (err.Fail()) { 862 | std::string e = "Failed to load vmlinux: "; 863 | e += err.GetCString(); 864 | res.AppendMessage(e.c_str()); 865 | } else { 866 | // optional: read "phys_base" from kdump attr 867 | auto physb_str = 868 | g_kdump.GetAttrString("linux.vmcoreinfo.NUMBER.phys_base"); 869 | if (!physb_str.empty()) { 870 | uint64_t pb = strtoull(physb_str.c_str(), nullptr, 0); 871 | if (pb) { 872 | // set section load address, etc. 873 | lldb::SBModule mod = target.GetModuleAtIndex(0); 874 | if (mod.IsValid()) { 875 | lldb::SBSection s_text = mod.FindSection("__text"); 876 | if (s_text.IsValid()) { 877 | lldb::SBError e2 = target.SetSectionLoadAddress(s_text, pb); 878 | if (e2.Fail()) { 879 | std::string w = "Could not set load address: "; 880 | w += e2.GetCString(); 881 | res.AppendMessage(w.c_str()); 882 | } 883 | } 884 | } 885 | } 886 | } 887 | } 888 | } 889 | 890 | char msg[256]; 891 | ::snprintf(msg, sizeof(msg), "Opened %s", vmcorePath.c_str()); 892 | res.AppendMessage(msg); 893 | return true; 894 | } 895 | }; 896 | 897 | //-------------------------------------------------------------------------------------- 898 | // Plugin entry 899 | //-------------------------------------------------------------------------------------- 900 | 901 | namespace lldb { 902 | 903 | bool PluginInitialize(lldb::SBDebugger debugger) { 904 | auto interp = debugger.GetCommandInterpreter(); 905 | debugger.SetPrompt("kLLDB> "); 906 | 907 | lldb::SBCommand cmdGroup = interp.AddMultiwordCommand( 908 | "kdump", "Commands for Linux vmcore/kdump analysis"); 909 | if (!cmdGroup.IsValid()) { 910 | ::fprintf(stderr, "Failed to create 'linux kdump' cmd group.\n"); 911 | return false; 912 | } 913 | 914 | cmdGroup.AddCommand("open", new CoreOpenCommand(), 915 | "Open a kdump file: kdump open "); 916 | cmdGroup.AddCommand("bugpoint", new CoreBugpointCommand(), 917 | "Show bugpoint based on rbp and rip"); 918 | cmdGroup.AddCommand("info", new CoreInfoCommand(), 919 | "Show top-level info from the dump"); 920 | cmdGroup.AddCommand("registers", new CoreRegistersCommand(), 921 | "Show registers content from dump file"); 922 | cmdGroup.AddCommand("load-lkm", new CoreLoadLKMCommand(), 923 | "Load a kernel module"); 924 | cmdGroup.AddCommand("source-dir-map", new CoreSourceDirMapCommand(), 925 | "Map remote source directories to local paths"); 926 | 927 | ::printf("kLLDBOffline plugin loaded.\n"); 928 | return true; 929 | } 930 | 931 | } // namespace lldb 932 | --------------------------------------------------------------------------------