我正在尝试在树莓派上实现一个裸机应用程序,并希望将stdout连接到迷你部件以进行调试。
我已经遵循了这里和这里概述的过程
我已经创建了一个似乎工作完美的uart_put函数,允许我将消息打印到我的PC的COM端口。然后我实现了_write系统调用,使它调用我的uart_put函数来输出。如果我向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 Worldrn"); // Works
}
while(1)
{
printf("Hello %srn", "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 Worldrn");
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 worldrn"); // Works
snprintf(s, 100, "hello %srn", "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 worldrn"); // Works
snprintf(s, 100, "hello %srn", "world"); // Doesn't work
snprintf(s, 100, "hello %drn", 1); // Doesn't work
char s[100];
char t[100];
strcpy(s, "hello worldrn");
snprintf(t, 100, s); // Doesn't work
这看起来不像是一个UART问题,而是一个库问题。
如果你想确保我的假设是正确的,直接调用_write()
,看看它是否有效。很可能会。另外,我假设您正在使用newlib
。
如果_write()
按预期工作,则问题仅限于printf
的上层。不幸的是,printf
就像一个洋葱,你必须一层一层地剥它,它会让你哭。
只是为了好玩,newlib
源代码的一个片段:
/*
* Actual printf innards.
*
* This code is large and complicated...
*/
幸运的是,仍然有一些方法来调试问题,而不会迷失在vfprintf.c
。也许最简单的起点是尝试snprintf()
,因为它没有内存管理问题。内存分配代码包括sbrk
之类的东西,这可能是一个问题。人们可能会认为内存管理是ok的,因为malloc()
似乎可以工作,但情况并非总是如此。(即使malloc()
给出了错误的地址,它也可能看起来很好,但是内存损坏会发生。)
让我们知道你得到了这些调试步骤!(我有根据的猜测是sbrk
由于某种原因不起作用,这破坏了内存管理。)
Update看起来问题不在于内存分配——至少不只是内存分配——我们需要解决这个问题。我希望你没有化太浓的妆。(这让我哭了,我不能100%确定下面的分析是正确的。所以还是少说点吧
当printf
被调用时,newlib
会发生什么?这个故事在newlib/libc/stidio
文件夹下的newlib
源代码中。
Layer 1: printf()
第一,printf.c
:
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;
}
非常简单。如果有什么地方出错了,它是:
-
_REENT_SMALL_CHECK_INIT(ptr);
或 - 变量的处理
我不认为这里的可重入性是一个问题,所以我将集中在变量上。也许这将是一个好主意,使最小的变量测试代码,这将显示,如果他们是坏的。(我不明白为什么它们会被破坏,但在嵌入式系统中,不做任何假设更安全。)
Layer 2: _vfprintf_r()
这是带有可重入代码的标准vfprintf
的内部版本(file- printf
的varargs-version)。它在vfprintf.c
中定义。根据库编译过程中使用的开关,它有几种不同的风格:STRING_ONLY
(没有内存分配)和/或NO_FLOATING_POINT
。我假设您有完整的版本,在这种情况下,可以在名称_VFPRINTF_R
下找到正确的函数(一些#define
正在进行中)。
代码不太容易阅读,函数的前几百行由声明许多变量(取决于编译选项)和十几个宏组成。然而,该函数真正做的第一件事是一个无限循环,以扫描格式字符串中的%
。当它找到一个