From df0c842b776308218a2d4a64d6bebb907e35a5b8 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sun, 19 May 2013 01:49:09 +0200 Subject: [PATCH] Add internal kernel debugger. --- sortix/Makefile | 1 + sortix/debugger.cpp | 645 ++++++++++++++++++++++++ sortix/include/sortix/kernel/debugger.h | 36 ++ sortix/kb/ps2.cpp | 9 +- 4 files changed, 690 insertions(+), 1 deletion(-) create mode 100644 sortix/debugger.cpp create mode 100644 sortix/include/sortix/kernel/debugger.h diff --git a/sortix/Makefile b/sortix/Makefile index f89ba975..3707280d 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -83,6 +83,7 @@ copy.o \ $(CPU)/calltrace.o \ $(CPU)/kthread.o \ crc32.o \ +debugger.o \ descriptor.o \ dispmsg.o \ dtable.o \ diff --git a/sortix/debugger.cpp b/sortix/debugger.cpp new file mode 100644 index 00000000..aeea8451 --- /dev/null +++ b/sortix/debugger.cpp @@ -0,0 +1,645 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + debugger.cpp + Internal kernel debugger. + +*******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kb/layout/us.h" + +namespace Sortix { +namespace Debugger { + +uint16_t* const VIDEO_MEMORY = (uint16_t*) 0xB8000; + +bool first_f10; +static int column; +static int row; +static Thread* current_thread; +#define current_process (current_thread->process) + +// Changes the position of the hardware cursor. +void SetCursor(int x, int y) +{ + unsigned value = x + y * 80; + + // This sends a command to indicies 14 and 15 in the + // CRT Control Register of the VGA controller. These + // are the high and low bytes of the index that show + // where the hardware cursor is to be 'blinking'. + CPU::OutPortB(0x3D4, 14); + CPU::OutPortB(0x3D5, (value >> 8) & 0xFF); + CPU::OutPortB(0x3D4, 15); + CPU::OutPortB(0x3D5, (value >> 0) & 0xFF); +} + +void GetCursor(int* x, int* y) +{ + CPU::OutPortB(0x3D4, 14); + uint8_t high = CPU::InPortB(0x3D5); + CPU::OutPortB(0x3D4, 15); + uint8_t low = CPU::InPortB(0x3D5); + unsigned value = high << 8 | low; + *x = value % 80; + *y = value / 80; +} + +uint16_t* Character(int x, int y) +{ + return &VIDEO_MEMORY[y * 80 + x]; +} + +void Scroll() +{ + for ( int y = 0; y < 25-1; y++ ) + for ( int x = 0; x < 80; x++ ) + *Character(x, y) = *Character(x, y+1); + for ( int x = 0; x < 80; x++ ) + *Character(x, 25-1) = 0x700 | ' '; +} + +void Newline() +{ + if ( row + 1 == 25 ) + Scroll(); + else + row++; + column = 0; +} + +void PrintChar(char c) +{ + if ( c == '\n' ) + Newline(); + else if ( c == '\b' ) + { + if ( column ) + column--; + else if ( row ) + column = 80, + row--; + *Character(column, row) = 0x700 | ' '; + } + else if ( c == '\r' ) + column = 0; + else if ( c == '\t' ) + { + do PrintChar(' '); + while ( column % 8 != 0 ); + } + else + { + if ( column == 80 ) + Newline(); + *Character(column++, row) = 0x700 | (uint16_t) c; + } + + SetCursor(column, row); +} + +void PrintString(const char* str) +{ + while ( *str ) + PrintChar(*str++); +} + +size_t PrintCallback(void* /*user*/, const char* str, size_t len) +{ + for ( size_t i = 0; i < len; i++ ) + PrintChar(str[i]); + return len; +} + +void Print(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + vprintf_callback(PrintCallback, NULL, format, ap); + va_end(ap); +} + +void PrintSymbol(const char* symbol) +{ + if ( !symbol ) + { + Print(""); + return; + } + if ( !(symbol[0] == '_' && symbol[1] == 'Z') ) + { + Print("%s(...)", symbol); + return; + } + symbol += 2; + while ( *symbol ) + { + if ( *symbol == 'N' ) + symbol++; + else if ( '0' <= *symbol && *symbol <= '9' ) + { + size_t len = strtoul(symbol, (char**) &symbol, 10); + for ( size_t i = 0; i < len; i++ ) + PrintChar(*symbol++); + if ( *symbol == 'L' ) + symbol++; // TODO: What is this? + if ( '0' <= *symbol && *symbol <= '9' ) + Print("::"); + } + else if ( *symbol == 'v' ) + { + symbol++; + Print("()"); + break; + } + else if ( *symbol == 'i' ) + { + symbol++; + Print("(...)"); + break; + } + else if ( *symbol == 'E' || *symbol == 'P' ) + { + symbol++; + if ( *symbol == 'v' ) + Print("()"); + else + Print("(...)"); + break; + } + else + PrintChar(*symbol++); + } +} + +static bool MatchesSymbol(const Symbol* symbol, uintptr_t address) +{ + return symbol->address <= address && + address <= symbol->address + symbol->size; +} + +const char* GetSymbolName(uintptr_t address) +{ + if ( const char* symbol_name = GetKernelSymbolName(address) ) + return symbol_name; + for ( size_t i = 0; i < current_process->symbol_table_length; i++ ) + { + const Symbol* symbol = current_process->symbol_table + i; + if ( MatchesSymbol(symbol, address) ) + return symbol->name; + } + return NULL; +} + +void ReadCommand(char* buffer, size_t buffer_length) +{ + KBLayoutUS kblayout; + bool scancode_escaped = false; + + size_t written = 0; + while ( true ) + { + // Get a scancode from the keyboard. + uint16_t iobase = 0x60; + const uint16_t DATA = 0x0; + //const uint16_t COMMAND = 0x0; + const uint16_t STATUS = 0x4; + while ( (CPU::InPortB(iobase + STATUS) & (1<<0)) == 0 ); + uint8_t scancode = CPU::InPortB(iobase + DATA); + + // Handle escaped scancodes. + const uint8_t SCANCODE_ESCAPE = 0xE0; + if ( (scancode_escaped = scancode == SCANCODE_ESCAPE) ) + continue; + + // Produce a format integer. + int offset = (scancode_escaped) ? 0x80 : 0; + int kbkey = scancode & 0x7F; + if ( scancode & 0x80 ) { kbkey = -kbkey - offset; } + else { kbkey = kbkey + offset; } + + if ( !written && kbkey == -KBKEY_F10 ) + { + if ( !first_f10 ) + { + strncpy(buffer, "exit", buffer_length); + break; + } + first_f10 = false; + } + + // Translate the keystroke into unicode. + uint32_t unicode = kblayout.Translate(kbkey); + + if ( !unicode ) + continue; + + // Ignore depressed keys. + if ( kbkey < 0 ) + continue; + + // Discard anything but ascii. + if ( 128 <= unicode ) + continue; + + char c = (char) unicode; + + // Discard tabs. + if ( c == '\t' ) + continue; + + // Handle backspace. + if ( c == '\b' ) + { + if ( !written ) + continue; + PrintChar(c); + written--; + continue; + } + + // Truncate the user input if it is too long. + if ( written == buffer_length && c != '\n' ) + continue; + + PrintChar(c); + + // Finish reading the line. + if ( c == '\n' ) + { + buffer[written] = '\0'; + break; + } + + buffer[written++] = c; + } +} + +Process* FindProcess(pid_t least_pid, pid_t max_pid) +{ + Process* best = NULL; + if ( least_pid <= current_process->pid && current_process->pid <= max_pid ) + best = current_process; + Thread* first_thread = current_thread; + for ( Thread* iter = first_thread->schedulerlistnext; + iter != first_thread; iter = iter->schedulerlistnext ) + if ( least_pid <= iter->process->pid && iter->process->pid <= max_pid && + (!best || iter->process->pid < best->pid) ) + best = iter->process; + return best; +} + +int ThreadId(Thread* thread) +{ + int ret = 0; + while ( thread->prevsibling ) + ret++, thread = thread->prevsibling; + return ret; +} + +int main_bt(int /*argc*/, char* /*argv*/[]) +{ + CPU::InterruptRegisters regs; + current_thread->LoadRegisters(®s); +#if defined(__x86_64__) + unsigned long ip = regs.rip; + unsigned long bp = regs.rbp; +#elif defined(__i386__) + unsigned long ip = regs.eip; + unsigned long bp = regs.ebp; +#endif + + bool userspace = false; + unsigned long depth = 0; + do + { + if ( 4*1024*1024 <= ip && !userspace ) + Print(" -- userspace --\n"), userspace = true; + const char* symbol = GetSymbolName(ip); + Print("%-4lu 0x%zx ", depth, ip); + PrintSymbol(symbol); + Print("\n"); + if ( !bp ) + break; + ip = ((unsigned long*) bp)[1]; + bp = ((unsigned long*) bp)[0]; + depth++; + } while ( ip ); + return 0; +} + +int main_dump(int argc, char* argv[]) +{ + unsigned long word_size = 0; + if ( !strcmp(argv[0], "dump8") ) word_size = 1; + if ( !strcmp(argv[0], "dump16") ) word_size = 2; + if ( !strcmp(argv[0], "dump32") ) word_size = 4; + if ( !strcmp(argv[0], "dump64") ) word_size = 8; + if ( argc < 2 ) + return 0; + + unsigned long start = strtoul(argv[1], NULL, 0); + unsigned long length = 1; + if ( 3 <= argc ) + length = strtoul(argv[2], NULL, 0); + + uint8_t* data = (uint8_t*) start; + for ( size_t i = 0; i < length; i++ ) + { + size_t elemlen = (word_size ? word_size : 1) ; + size_t outlen = elemlen * 2; + if ( 80 - column < (int) outlen ) + Print("\n"); + if ( !column ) +#if __WORDSIZE == 64 + Print("%016lX: ", start + i * elemlen); +#else + Print("%08lX: ", start + i * elemlen); +#endif + else if ( word_size ) + Print(" "); + // TODO: Endianness! + for ( size_t n = 0; n < (word_size ? word_size : 1); n++ ) + Print("%02X", data[i * elemlen + n]); + } + + if ( column ) + Print("\n"); + + return 0; +} + +int main_echo(int argc, char* argv[]) +{ + const char* prefix = ""; + for ( int i = 1; i < argc; i++ ) + Print("%s%s", prefix, argv[i]), + prefix = " "; + Print("\n"); + return 0; +} + +int main_exit(int /*argc*/, char* /*argv*/[]) +{ + return -1; +} + +int main_pid(int argc, char* argv[]) +{ + if ( 2 <= argc ) + { + int pid = atoi(argv[1]); + Process* process = FindProcess(pid, pid); + if ( !process ) + { + Print("pid: %i: No such process\n", pid); + return 1; + } + current_thread = process->firstthread; + Memory::SwitchAddressSpace(current_thread->addrspace); + } + Print("%c %i\t`%s'\n", '*', (int) current_process->pid, + current_process->program_image_path); + return 0; +} + +int main_ps(int /*argc*/, char* /*argv*/[]) +{ + pid_t least = 0; + while ( Process* process = FindProcess(least, INT_MAX) ) + { + Print("%c %i\t`%s'\n", process == current_process ? '*' : ' ', + (int) process->pid, process->program_image_path); + least = process->pid + 1; + } + return 0; +} + +int main_rs(int /*argc*/, char* /*argv*/[]) +{ + CPU::InterruptRegisters regs; + current_thread->LoadRegisters(®s); +#if defined(__x86_64__) + Print("rax=0x%lx, ", regs.rax); + Print("rbx=0x%lx, ", regs.rbx); + Print("rcx=0x%lx, ", regs.rcx); + Print("rdx=0x%lx, ", regs.rdx); + Print("rdi=0x%lx, ", regs.rdi); + Print("rsi=0x%lx, ", regs.rsi); + Print("rsp=0x%lx, ", regs.userrsp); + Print("rbp=0x%lx, ", regs.rbp); + Print("r8=0x%lx, ", regs.r8); + Print("r9=0x%lx, ", regs.r9); + Print("r10=0x%lx, ", regs.r10); + Print("r11=0x%lx, ", regs.r11); + Print("r12=0x%lx, ", regs.r12); + Print("r13=0x%lx, ", regs.r13); + Print("r14=0x%lx, ", regs.r14); + Print("r15=0x%lx, ", regs.r15); + Print("rip=0x%lx, ", regs.rip); + Print("rflags=0x%lx, ", regs.rflags); + Print("int_no=%lu, ", regs.int_no); + Print("err_code=0x%lx, ", regs.err_code); + Print("cs=0x%lx, ", regs.cs); + Print("ds=0x%lx, ", regs.ds); + Print("ss=0x%lx, ", regs.ss); + Print("kerrno=%lu, ", regs.kerrno); + Print("cr2=%lx, ", regs.cr2); + Print("signal_pending=%lu.", regs.signal_pending); +#elif defined(__i386__) + Print("eax=0x%lx, ", regs.eax); + Print("ebx=0x%lx, ", regs.ebx); + Print("ecx=0x%lx, ", regs.ecx); + Print("edx=0x%lx, ", regs.edx); + Print("edi=0x%lx, ", regs.edi); + Print("esi=0x%lx, ", regs.esi); + Print("esp=0x%lx, ", regs.useresp); + Print("ebp=0x%lx, ", regs.ebp); + Print("eip=0x%lx, ", regs.eip); + Print("eflags=0x%lx, ", regs.eflags); + Print("int_no=%lu, ", regs.int_no); + Print("err_code=0x%lx, ", regs.err_code); + Print("cs=0x%lx, ", regs.cs); + Print("ds=0x%lx, ", regs.ds); + Print("ss=0x%lx, ", regs.ss); + Print("kerrno=%lu, ", regs.kerrno); + Print("cr2=%lx, ", regs.cr2); + Print("signal_pending=%lu.", regs.signal_pending); +#endif + Print("\n"); + return 0; +} + +static void DescribeThread(int tid, Thread* thread) +{ + CPU::InterruptRegisters regs; + thread->LoadRegisters(®s); +#if defined(__x86_64__) + unsigned long ip = regs.rip; +#elif defined(__i386__) + unsigned long ip = regs.eip; +#endif + + Print("%c ", thread == current_thread ? '*' : ' '); + Print("%i", tid); + Print("\tip=0x%lx", ip); + Print("\n"); +} + +int main_tid(int argc, char* argv[]) +{ + if ( 2 <= argc ) + { + int tid = atoi(argv[1]); + Thread* thread = current_process->firstthread; + for ( int i = 0; i < tid && thread; i++ ) + thread = thread->nextsibling; + if ( !thread ) + { + Print("tid: %i: No such thread\n", tid); + return 1; + } + current_thread = thread; + Memory::SwitchAddressSpace(current_thread->addrspace); + } + DescribeThread(ThreadId(current_thread), current_thread); + return 0; +} + +int main_ts(int /*argc*/, char* /*argv*/[]) +{ + int tid = 0; + for ( Thread* thread = current_process->firstthread; thread; thread = thread->nextsibling ) + DescribeThread(tid++, thread); + return 0; +} + +struct command_registration +{ + const char* command; + int (*function)(int, char*[]); + const char* help; +}; + + +static const struct command_registration commands[] = +{ + "bt", main_bt, "bt Stack trace", + "dump", main_dump, "dump START [LEN] Dump continuous memory", + "dump8", main_dump, "dump8 START [LEN] Dump 8-bit memory words", + "dump16", main_dump, "dump16 START [LEN] Dump 16-bit memory words", + "dump32", main_dump, "dump32 START [LEN] Dump 32-bit memory words", + "dump64", main_dump, "dump16 START [LEN] Dump 64-bit memory words", + "echo", main_echo, "echo [ARG...] Echo string", + "exit", main_exit, "exit Quit debugger", + "pid", main_pid, "pid [NEWPID] Change current process", + "ps", main_ps, "ps List processes", + "rs", main_rs, "rs Print registers", + "tid", main_tid, "tid [NEWTID] Change current thread", + "ts", main_ts, "ts List threads in current process", + NULL, NULL, NULL, +}; + +bool RunCommand() +{ + const size_t BUFFER_LENGTH = 256; + static char buffer[BUFFER_LENGTH]; + Print("> "); + ReadCommand(buffer, BUFFER_LENGTH-1); + + static char* argv[256]; + + int argc = 0; + char* input = buffer; + char* saved = NULL; + while ( (argv[argc] = strtok_r(input, " \t\n", &saved)) ) + argc++, input = NULL; + + if ( !argc ) + return true; + + if ( !strcmp(argv[0], "help") ) + { + Print("You can use the following kernel debugger commands:\n"); + for ( size_t i = 0; commands[i].command; i++ ) + Print("%s\n", commands[i].help); + Print("\n"); + return true; + } + + for ( size_t i = 0; commands[i].command; i++ ) + if ( !strcmp(argv[0], commands[i].command) ) + return commands[i].function(argc, argv) != -1; + + Print("%s: No such kernel debugger command\n", argv[0]); + + return true; +} + +void Run() +{ + static uint16_t saved_video_memory[80*25]; + + current_thread = CurrentThread(); + + bool was_enabled = Interrupt::SetEnabled(false); + + first_f10 = true; + + addr_t saved_addrspace = current_thread->addrspace; + + memcpy(saved_video_memory, VIDEO_MEMORY, sizeof(saved_video_memory)); + int saved_x, saved_y; + GetCursor(&saved_x, &saved_y); + + column = saved_x, row = saved_y; + Print("\nSortix kernel debugger - type `help' for help.\n"); + + while ( RunCommand() ); + SetCursor(saved_x, saved_y); + memcpy(VIDEO_MEMORY, saved_video_memory, sizeof(saved_video_memory)); + + Memory::SwitchAddressSpace(saved_addrspace); + + Interrupt::SetEnabled(was_enabled); +} + +} // namespace Debugger +} // namespace Sortix diff --git a/sortix/include/sortix/kernel/debugger.h b/sortix/include/sortix/kernel/debugger.h new file mode 100644 index 00000000..cb272936 --- /dev/null +++ b/sortix/include/sortix/kernel/debugger.h @@ -0,0 +1,36 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + sortix/kernel/debugger.h + Internal kernel debugger. + +*******************************************************************************/ + +#ifndef INCLUDE_SORTIX_KERNEL_DEBUGGER_H +#define INCLUDE_SORTIX_KERNEL_DEBUGGER_H + +namespace Sortix { +namespace Debugger { + +void Run(); + +} // namespace Debugger +} // namespace Sortix + +#endif diff --git a/sortix/kb/ps2.cpp b/sortix/kb/ps2.cpp index 9b2b7068..ff5651fa 100644 --- a/sortix/kb/ps2.cpp +++ b/sortix/kb/ps2.cpp @@ -23,8 +23,10 @@ *******************************************************************************/ #include +#include #include #include +#include #include #include @@ -88,9 +90,14 @@ namespace Sortix work->kb->InterruptWork(work->scancode); } - void PS2Keyboard::OnInterrupt(CPU::InterruptRegisters* /*regs*/) + void PS2Keyboard::OnInterrupt(CPU::InterruptRegisters* regs) { uint8_t scancode = PopScancode(); + if ( scancode == KBKEY_F10 ) + { + CurrentThread()->SaveRegisters(regs); + Debugger::Run(); + } PS2KeyboardWork work; work.kb = this; work.scancode = scancode;