我正在尝试在raspberry pi上实现一个裸机应用程序,并希望将stdout连接到mini uart以进行调试。
的流程我创建了一个uart_putc函数,它似乎工作得很好,允许我将消息打印到我的PC的COM端口。然后我实现了_write系统调用,使其调用我的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 */
}
的start.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
答案 0 :(得分:2)
这看起来不像UART问题,而是库问题。
如果您想确保我的假设是正确的,请直接致电_write()
并查看是否有效。最有可能的是。另外,我假设您使用的是newlib
。
如果_write()
按预期工作,则问题仅限于printf
的上层。不幸的是printf
就像一个洋葱,你必须逐层剥离它,它会让你哭泣。
只是为了好玩,来自newlib
源代码的片段:
/*
* Actual printf innards.
*
* This code is large and complicated...
*/
幸运的是,仍有一些方法可以调试问题而不会迷失vfprintf.c
。也许最简单的出发点是尝试snprintf()
,因为它缺乏内存管理问题。内存分配代码包括诸如sbrk
之类的内容,这可能是一个问题。人们很容易认为内存管理是可以的,因为malloc()
似乎有效,但情况并非总是如此。 (malloc()
即使它提供了错误的地址也可能看起来不错,但会发生内存损坏。)
告诉我们您在这些调试步骤中的位置! (我的有根据的猜测是,sbrk
由于某种原因不起作用,这会破坏内存管理。)
更新因为看起来问题不在于内存分配 - 至少不仅仅是内存分配 - 我们需要解决洋葱。我希望你不要穿太重的化妆品......(这让我哭了,我不能100%肯定下面的分析是正确的。所以带上一点盐。)
调用newlib
时printf
会发生什么?故事位于文件夹newlib
中的newlib/libc/stidio
来源。
第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);
或我不认为重新入侵是一个问题,所以我会专注于varargs。也许最好制作一个最小的varargs测试代码,然后显示它们是否已损坏。 (我不明白为什么它们会被破坏,但在嵌入式系统中,不假设任何东西都更安全。)
第2层:_vfprintf_r()
这是标准vfprintf
(varargs-version of file - printf
)的内部版本,带有可重入代码。它在vfprintf.c
中定义。它有多种风格,具体取决于库编译期间使用的开关:STRING_ONLY
(无内存分配)和/或NO_FLOATING_POINT
。我假设您有完整版本,在这种情况下,可以在名称_VFPRINTF_R
下找到正确的函数(某些#define
正在进行中。)
代码不容易阅读,函数的前几百行包括声明许多变量(取决于编译选项)和十几个宏。但是,该函数真正做的第一件事是无限循环来扫描%
的格式字符串。当它找到\0
时,它确实goto done;
(是的,它还有goto
s - 我想把它扔给代码审查...)
然而,这给了我们一个线索:如果我们不添加任何额外的参数,我们只需跳转到done
我们有一些清理。我们可以生存,但不能处理任何格式参数。那么,让我们看一下%s
最终会在哪里。这是按照人们的预期完成的,有一个很大的switch(ch) ...
。在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;
(现在,我假设您没有在MB_CAPABLE
中启用多字节字符串支持newlib
。如果有,那么事情就变得更复杂了。)其余部分看起来很容易调试(strlen
和memchr
),但GET_ARG
宏可能很复杂 - 再次取决于您的编译设置(如果您有_NO_POS_ARGS
,则更简单)。< / p>
切换后,简单的情况(格式字符串中没有填充)是:
PRINT (cp, size);
这是打印宏。至少如果指针cp
是错误的,那么奇怪的事情就会发生。
宏本身并不是非常疯狂,因为我们可以打印简单的案例;只有参数引起问题。
我担心调试有点棘手,但症状指向内存中被破坏的东西。要检查的一件事是printf
的返回值。它应该返回打印的字符数。返回值是否合理有助于调试其余部分。
答案 1 :(得分:2)
对不起,我为你解决这个问题已经很晚了。我是valvers.com裸机教程的作者。崩溃的原因是我所知道的,但没有时间解决。实际上,我不知道它会解决你的问题。
简而言之,问题是我们告诉工具链处理器是ARM1176,更重要的是浮点单元是VFP,我们应该使用硬浮ABI。
使用VFP是一个重要选项 - 这意味着我们选择了使用此选项编译的C库。通常不使用VFP指令,因此不会使我们失望。显然,printf的某些部分确实使用了VFP指令。
这让我们失望的原因是因为负责生成良好C运行时环境的启动汇编程序不启用VFP,因此当您到达VFP指令时,处理器会跳转到未定义的指令异常向量。
这就是我发现这就是问题的方法。我简单地在任何异常向量中启用了LED,并在使用printf格式时点亮。然后是在异常向量中移除LED调用直到它不再亮的情况。这发生在“未定义指令”例外中。在ARM站点上快速搜索显示,如果遇到VFP指令并且未启用VFP,处理器将进入此处。因此,它提醒我解决这个问题!
解决方案
您需要做一些事情。您需要将CMAKE_C_FLAGS复制到CMakeLists.txt文件中的CMAKE_ASM_FLAGS,以便将正确的选项传递给汇编程序,目前它们不是!我会尽快更新教程来解决这个问题!
在CMakeLists.txt文件中的最后set( CMAKE_C_FLAGS ... )
命令的正下方添加set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} )
,因为CMake使用gcc作为汇编程序,因此可以正常工作。
接下来,我们需要修改启动汇编程序文件(在我的教程armc-nnn-start.S中)以启用VFP。在bl _cstartup
(这直接取决于TI Website)
// Enable VFP/NEON
// r1 = Access Control Register
MRC p15, #0, r1, c1, c0, #2
// enable full access for p10,11
ORR r1, r1, #(0xf << 20)
// ccess Control Register = r1
MCR p15, #0, r1, c1, c0, #2
MOV r1, #0
// flush prefetch buffer because of FMXR below
MCR p15, #0, r1, c7, c5, #4
// and CP 10 & 11 were only just enabled
// Enable VFP itself
MOV r0,#0x40000000
// FPEXC = r0
FMXR FPEXC, r0
您可以从ARM找到有关此here的一些信息。
这些更改足以使printf格式化工作正常(我已在UART上测试过它)。如果您有任何其他问题,请不要犹豫。
最后,抱歉,你因为启动代码不正确而感到悲伤!我想做的最后一件事是花费一些时间!!