我试图在raspberry pi上实现一个裸机应用程序,并希望将stdout连接到mini-uart以进行调试。
我遵循了herehere
我已经创建了一个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_CAPABLEnewlib),但是strlen宏可能会很复杂——这取决于您的编译设置(如果您有memchr,它会简单得多)。
切换之后,简单的大小写(格式字符串中没有填充)是:
PRINT (cp, size);

这是打印宏。至少如果指针GET_ARG是错误的,那么奇怪的事情就会发生。
宏本身不能疯狂得可怕,因为我们可以打印简单的案例;只有参数会导致问题。
恐怕这对调试来说有点棘手,但症状指向内存中的某些东西正在损坏。要检查的一件事是_NO_POS_ARGS的返回值。它应该返回打印的字符数。返回值是否正常有助于调试其他值。

10-06 09:45