0

我正在尝试在树莓派上实现一个裸机应用程序,并希望将标准输出连接到迷你 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 */
}

开始.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
4

2 回答 2

3

很抱歉我来晚了才为你解决这个问题。我是 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 以便将正确的选项传递给汇编器,目前它们不是!我会尽快更新教程来解决这个问题!

set( CMAKE_C_FLAGS ... )就在 CMakeLists.txt 文件中的最后一个命令下方,添加set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} )可以正常工作,因为 CMake 使用 gcc 作为汇编程序。

接下来,我们需要修改启动汇编文件(在我的教程 armc-nnn-start.S 中)以启用 VFP。在上面插入下面的代码bl _cstartup

(这是直接关闭TI 网站

// 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 提供的一些相关信息。

这些更改足以使 printf 格式正常工作(我已经在 UART 上对其进行了测试)。如果您还有其他问题,请随时询问。

最后,对不起,因为启动代码不正确,让您感到悲伤!我最不想做的就是花费别人的时间!!

于 2014-12-02T19:57:37.350 回答
3

这看起来不像是 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);或者
  • 可变参数的处理

我不认为重入是一个问题,所以我会专注于可变参数。也许制作一个最小的可变参数测试代码是个好主意,然后它会显示它们是否被破坏。(我不明白为什么它们会被破坏,但在嵌入式系统中,不做任何假设会更安全。)

第 2 层:_vfprintf_r()

这是标准的内部版本vfprintf(file- 的 varargs 版本printf),带有可重入代码。它定义在vfprintf.c. 它有几种风格,具体取决于在库编译期间使用了哪些开关:(STRING_ONLY无内存分配)和/或NO_FLOATING_POINT. 我假设您拥有完整版本,在这种情况下,可以在名称下找到正确的功能_VFPRINTF_R(一些#defineing 一直在进行)。

代码不太容易阅读,函数的前几百行由声明许多变量(取决于编译选项)和十几个宏组成。但是,该函数真正做的第一件事是无限循环扫描格式字符串以查找%. 当它找到 a 时\0,它会goto done;(是的,它也有gotos ——我想把它扔给代码审查......)

然而,这给了我们一个线索:如果我们不添加任何额外的参数,我们只是跳转到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.取决于你的编译设置(如果你有,它会简单得多)。strlenmemchrGET_ARG_NO_POS_ARGS

切换后,简单的情况(格式字符串中没有填充)是:

PRINT (cp, size);

这是打印宏。至少如果指针cp是错误的,那么奇怪的事情就会发生。

宏本身不能太疯狂,因为我们可以打印简单的案例;只有论据会引起问题。

恐怕这对调试有点棘手,但症状表明内存中的某些内容已损坏。要检查的一件事是您的printf. 它应该返回打印的字符数。返回值是否正常有助于调试其余部分。

于 2014-07-05T22:07:09.637 回答