我试图在raspberry pi上实现一个裸机应用程序,并希望将stdout连接到mini-uart以进行调试。
我遵循了here和here
我已经创建了一个uart-putc函数,它似乎工作得很好,允许我将消息打印到我的PC的COM端口。然后我实现了写系统调用,让它调用我的uart putc函数进行输出。如果我将一个字符串文字传递到printf附加文字参数或任何非文字参数,则可以正常工作。没有任何内容会打印到串行端口,并且在几次调用之后,应用程序将挂起。
有人知道会出什么问题吗?如果需要,很乐意提供进一步的信息。。。
void uart_putc(char c)
{
while(1)
{
if(aux[AUX_MU_LSR]&0x20) break;
led_blink(); // Blink the LED off then on again to
// make sure we aren't stuck in here
}
aux[AUX_MU_IO] = c;
}
...
int _write(int file, char* ptr, int len)
{
int todo;
for (todo = 0; todo < len; todo++)
{
uart_putc(*ptr++);
}
return len;
}
...
while(1)
{
printf("Hello World\r\n"); // Works
}
while(1)
{
printf("Hello %s\r\n", "World"); // This doesn't print anything
// and will hang after about five calls
}
char* s = (char*)malloc(sizeof(char) * 100); // Heap or stack it doesn't matter
strcpy(s, "Hello World\r\n");
while(1)
{
printf(s); // This doesn't print anything
// and will hang after about five calls
}
while(1)
{
for (i = 0; i < 13; i++)
{
uart_putc(s[i]); // Works
}
}
更新
我正在使用newlib,当直接调用时,write可以正常工作。snprintf似乎也有同样的问题,即。
snprintf(s, 100, "hello world\r\n"); // Works
snprintf(s, 100, "hello %s\r\n", "world"); // Doesn't work
我对sbrk的实现是从我的OP中引用的页面中截取的
char *heap_end = 0;
caddr_t _sbrk(int incr) {
extern char heap_low; /* Defined by the linker */
extern char heap_top; /* Defined by the linker */
char *prev_heap_end;
if (heap_end == 0)
{
heap_end = &heap_low;
}
prev_heap_end = heap_end;
if (heap_end + incr > &heap_top)
{
/* Heap and stack collision */
return (caddr_t)0;
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
链接脚本
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
"elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x8000)); . = SEGMENT_START("text-segment", 0x8000);
. = 0x8000;
.ro : {
*(.text.startup)
*(.text)
*(.rodata)
}
.rw : {
*(.data)
__bss_start__ = .;
*(.bss)
__bss_end__ = .;
*(COMMON)
}
. = ALIGN(8);
heap_low = .; /* for _sbrk */
. = . + 0x10000; /* 64kB of heap memory */
heap_top = .; /* for _sbrk */
. = . + 0x10000; /* 64kB of stack memory */
stack_top = .; /* for startup.s */
}
开始时间
.section ".text.startup"
.global _start
_start:
ldr sp, =stack_top
// The c-startup
b _cstartup
_inf_loop:
b _inf_loop
更新2
涉及snprintf的进一步实验:
snprintf(s, 100, "hello world\r\n"); // Works
snprintf(s, 100, "hello %s\r\n", "world"); // Doesn't work
snprintf(s, 100, "hello %d\r\n", 1); // Doesn't work
char s[100];
char t[100];
strcpy(s, "hello world\r\n");
snprintf(t, 100, s); // Doesn't work
最佳答案
这看起来不像是UART问题,而是库问题。
如果你想确定我的假设是正确的,直接打电话给_write()
,看看是否有效。很可能会的。另外,我假设您正在使用newlib
。
如果_write()
按预期工作,则问题仅限于printf
的上层。不幸的是,printf
就像洋葱,你必须一层一层地剥开它,它会让你哭泣。
有趣的是,源代码中的一个片段:
/*
* Actual printf innards.
*
* This code is large and complicated...
*/
幸运的是,仍然有一些方法可以调试问题,而不会丢失到
newlib
中。也许最简单的起点是尝试vfprintf.c
,因为它缺少内存管理问题。内存分配代码包括snprintf()
之类的内容,这可能是一个问题。人们可能会认为内存管理是可以的,因为sbrk
似乎可以工作,但事实并非总是如此。(malloc()
即使给出了错误的地址,看起来也没问题,但会发生内存损坏。)让我们知道这些调试步骤的作用!(我有根据的猜测是,
malloc()
由于某种原因不起作用,这会破坏内存管理。)更新,似乎问题不在内存分配——至少不在内存分配——我们需要解决洋葱。我希望你不要化太浓的妆。。。(这让我哭了,我不确定下面的分析是否正确。所以要加一点盐。)
当调用
sbrk
时,newlib
中会发生什么?这个故事在printf
文件夹中的newlib
源中。第一层:
newlib/libc/stidio
首先,
printf()
:int
_DEFUN(printf, (fmt),
const char *__restrict fmt _DOTS)
{
int ret;
va_list ap;
struct _reent *ptr = _REENT;
_REENT_SMALL_CHECK_INIT (ptr);
va_start (ap, fmt);
ret = _vfprintf_r (ptr, _stdout_r (ptr), fmt, ap);
va_end (ap);
return ret;
}
很简单。如果出了什么问题,要么是:
printf.c
或瓦拉格斯的处理
我不认为重新进入是一个问题,所以我会集中精力在瓦拉格斯。也许制作一个最小的varargs测试代码是一个好主意,然后显示它们是否被破坏。(我不明白它们为什么会坏掉,但在嵌入式系统中,不做任何假设更安全。)
第二层:
_REENT_SMALL_CHECK_INIT(ptr);
这是标准
_vfprintf_r()
(varargs版本的文件-vfprintf
)的内部版本,带有重入代码。它在printf
中定义。根据库编译期间使用了哪些开关,它有几种风格:vfprintf.c
(无内存分配)和/或STRING_ONLY
。我假设您有完整的版本,在这种情况下,可以在名称NO_FLOATING_POINT
下找到正确的函数(一些_VFPRINTF_R
正在进行)。代码不太容易阅读,函数的前几百行由声明许多变量(取决于编译选项)和十几个宏组成。然而,函数真正做的第一件事是无休止地循环扫描
#define
的格式字符串。当它找到一个%
时,它会找到一个\0
(是的,它也有goto done;
s——我想把这个放到代码评审中…)然而,这给了我们一个线索:如果我们不添加任何额外的参数,我们只需跳到
goto
进行一些清理。我们可以生存,但不能处理任何格式参数。所以,让我们看看done
会在哪里结束。正如人们所料,这是一个很大的%s
。它说: case 's':
cp = GET_ARG (N, ap, char_ptr_t);
sign = '\0';
if (prec >= 0) {
/*
* can't use strlen; can only look for the
* NUL in the first `prec' characters, and
* strlen () will go further.
*/
char *p = memchr (cp, 0, prec);
if (p != NULL) {
size = p - cp;
if (size > prec)
size = prec;
} else
size = prec;
} else
size = strlen (cp);
break;
(现在,我假设您的
switch(ch) ...
中没有打开多字节字符串支持s
。如果有的话,事情就变得更复杂了。)剩下的看起来很容易调试(MB_CAPABLE
和newlib
),但是strlen
宏可能会很复杂——这取决于您的编译设置(如果您有memchr
,它会简单得多)。切换之后,简单的大小写(格式字符串中没有填充)是:
PRINT (cp, size);
这是打印宏。至少如果指针
GET_ARG
是错误的,那么奇怪的事情就会发生。宏本身不能疯狂得可怕,因为我们可以打印简单的案例;只有参数会导致问题。
恐怕这对调试来说有点棘手,但症状指向内存中的某些东西正在损坏。要检查的一件事是
_NO_POS_ARGS
的返回值。它应该返回打印的字符数。返回值是否正常有助于调试其他值。