冬天OS(二十二):assert / panic

--------------------------------------------------------

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 情况有点复杂:

  1. sys_printx 是内核态的函数,这是事先要知道的!
  2. 如果 ring1~ring3 特权级下调用,就会发生 ring1~ring3 -》 ring0,那么就会切换cs、ip,因此需要借助切换前 ring0~ring1 的 cs 将 s 的线性地址转换为物理地址!
  3. 如果 ring0 特权级下调用,那么 s 的地址就是物理地址!
  4. sys_printx 还会判断参数字符串是否请求 panic 或者 assert!针对 panic 会直接死在sys_printx,也就是内核态,针对 assert还会分情况对待!

 

测试:

 1,用户进程里调用 assert——阻塞用户进程

 

 

——TestA 虽然阻塞了,但是其余进程和任务不会受到影响!

 

2,系统任务里调用 assert——阻塞在 kernel

 

3,调用 panic——阻塞在 kernel

 

哦可...!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值