--------------------------------------------------------
assert / panic
--------------------------------------------------------
这节我们建立两个函数[assert / panic] 并完善 printf 系列,它们是后面的开发检错和调试不错的手段!
·assert
#define ASSERT
#ifdef ASSERT
void assertion_failure(char *exp, char *file, char *base_file, int line);
#define assert(exp) if (exp) ; \
else assertion_failure(#exp, __FILE__, __BASE_FILE__, __LINE__)
#else
#define assert(exp)
#endif
——assert 宏调用了函数 assertion_failure!
·vsprintf
PUBLIC int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
va_list p_next_arg = args;
int m;
char inner_buf[STR_DEFAULT_LEN/* 1024 */];
char cs;
int align_nr;
// 简单而言就是通过 fmt 的格式字符带着 args参数 来构造 buf 缓冲区!
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
else { /* a format string begins */
align_nr = 0;
}
fmt++;
// 如果遇见双百分号的话就写入一个百分号
if (*fmt == '%') {
*p++ = *fmt;
continue;
}
else if (*fmt == '0') {
cs = '0';
fmt++;
}
else {
cs = ' ';
}
// 如果 %123x 的话,align_nr 就是 123
while (((unsigned char)(*fmt) >= '0') && ((unsigned char)(*fmt) <= '9')) {
align_nr *= 10;
align_nr += *fmt - '0';
fmt++;
}
char * q = inner_buf;
memset(q, 0, sizeof(inner_buf));
switch (*fmt) {
case 'c':
*q++ = *((char*)p_next_arg);
p_next_arg += 4;
break;
case 'x':
m = *((int*)p_next_arg);
// 转换为 16 进制的字符
i2a(m, 16, &q);
p_next_arg += 4;
break;
case 'd':
m = *((int*)p_next_arg);
if (m < 0) {
m = m * (-1);
*q++ = '-';
}
// 转换为 10 进制的字符
i2a(m, 10, &q);
p_next_arg += 4;
break;
case 's':
strcpy(q, (*((char**)p_next_arg)));
q += strlen(*((char**)p_next_arg));
p_next_arg += 4;
break;
default:
break;
}
// 根据 align_nr 的值来填充多少空格!
int k;
for (k = 0; k < ((align_nr > strlen(inner_buf)) ? (align_nr - strlen/* 不是 size */(inner_buf)) : 0); k++) {
*p++ = cs;
}
// 把前面格式化的参数写入对齐后的缓冲区中!
q = inner_buf;
while (*q) {
*p++ = *q++;
}
}
*p = 0;
return (p - buf);
}
——vsprintf 作用就是格式化字符串!
·sprintf
int sprintf(char *buf, const char *fmt, ...)
{
va_list arg = (va_list)((char*)(&fmt) + 4); /* 4 ??? fmt ???????? */
return vsprintf(buf, fmt, arg);
}
——sprintf 会将格式化后的字符串交给用户!
·panic
PUBLIC void panic(const char *fmt, ...)
{
int i;
char buf[256];
/* 4 is the size of fmt in the stack */
va_list arg = (va_list)((char*)&fmt + 4);
// 先把用户字符串格式化
i = vsprintf(buf, fmt, arg);
// %c !!panic!! + 格式化的字符串
printl("%c !!panic!! %s", MAG_CH_PANIC, buf);
/* should never arrive here */
__asm__ __volatile__("ud2");
}
——panic 函数的作用给字符串加上 panic 标志,然后交给 printl 触发奔溃!
· assertion_failure
PUBLIC void assertion_failure(char *exp, char *file, char *base_file, int line)
{
printl("%c assert(%s) failed: file: %s, base_file: %s, ln%d",
MAG_CH_ASSERT,
exp, file, base_file, line);
// 用户进程的会会在这里死循环
spin("assertion_failure()");
// 不可能执行到这里
__asm__ __volatile__("ud2");
}
PUBLIC void spin(char * func_name)
{
printl("\nspinning in %s ...\n", func_name);
while (1) {}
}
——给字符串加上 assert 标志,printl 可能返回到 spin 之后再死循环,也可能就在 printl 中死在内核态,这取决于调用 assertion_failure 函数的是用户进程还是系统任务!
·printl
#define printl printf
·printf
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4); /*4是参数fmt所占堆栈中的大小*/
i = vsprintf(buf, fmt, arg);
buf[i] = 0;
printx(buf);
return i;
}
——printf 将触发系统调用!
·sys_printx
PUBLIC int sys_printx(int _unused1, int _unused2, char* s, struct proc* p_proc)
{
const char * p;
char ch;
char reenter_err[] = "? k_reenter is incorrect for unknown reason";
reenter_err[0] = MAG_CH_PANIC;
// 因为 sys_printx 是个系统调用,所以,进入 sys_printx 就意味着已经进入了一次中断!
if (k_reenter == 0) /* printx() called in Ring<1~3> */
p = va2la(proc2pid(p_proc), s);
else if (k_reenter > 0) /* printx() called in Ring<0> */
p = s;
else /* this should NOT happen */
p = reenter_err;
// 任务的 assert 和 内核的panic 都会死在这里!
if ((*p == MAG_CH_PANIC) ||
(*p == MAG_CH_ASSERT && p_proc_ready < &proc_table[NR_TASKS])) {
disable_int();
char * v = (char*)V_MEM_BASE;
const char * q = p + 1; /* +1: skip the magic char */
// 对整个显存进行污染!
while (v < (char*)(V_MEM_BASE + V_MEM_SIZE)) {
*v++ = *q++;
*v++ = RED_CHAR;
if (!*q) {
// 输出 8 行灰色 ' '
while (((int)v - V_MEM_BASE) % (SCR_WIDTH * 16/* 8 * 2 */)) {
v++;/* *v++ = ' '; */
*v++ = GRAY_CHAR;
}
// 重新开始
q = p + 1;
}
}
// 在内核态 hlt
__asm__ __volatile__("hlt");
}
while ((ch = *p++) != 0) {
// 用户态下跳过正常打印
if (ch == MAG_CH_PANIC || ch == MAG_CH_ASSERT)
continue; /* skip the magic char */
out_char(tty_table[p_proc->nr_tty].p_console, ch);
}
return 0;
}
——sys_printx 情况有点复杂:
- sys_printx 是内核态的函数,这是事先要知道的!
- 如果 ring1~ring3 特权级下调用,就会发生 ring1~ring3 -》 ring0,那么就会切换cs、ip,因此需要借助切换前 ring0~ring1 的 cs 将 s 的线性地址转换为物理地址!
- 如果 ring0 特权级下调用,那么 s 的地址就是物理地址!
- sys_printx 还会判断参数字符串是否请求 panic 或者 assert!针对 panic 会直接死在sys_printx,也就是内核态,针对 assert还会分情况对待!
测试:
1,用户进程里调用 assert——阻塞用户进程
——TestA 虽然阻塞了,但是其余进程和任务不会受到影响!
2,系统任务里调用 assert——阻塞在 kernel
3,调用 panic——阻塞在 kernel
哦可...!