/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013, 2014.
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.rsp);
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.esp);
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