C语言 应用程序挂起时,调用打印与裸机树莓派



我正在尝试在树莓派上实现一个裸机应用程序,并希望将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正在进行中)。

代码不太容易阅读,函数的前几百行由声明许多变量(取决于编译选项)和十几个宏组成。然而,该函数真正做的第一件事是一个无限循环,以扫描格式字符串中的%。当它找到一个代替,它做goto done;(是的,它有goto s,以及-我想把它扔到代码审查…)

然而,这给了我们一个提示:如果我们不添加任何额外的参数,我们只是跳到done,在那里我们做了一些清理。我们可以这样做,但不能处理任何格式参数。那么,让我们看看%s最终会在哪里结束。这是按照预期完成的,有一个大的switch(ch) ...。在s,它说:

    case 's':
        cp = GET_ARG (N, ap, char_ptr_t);
        sign = '';
        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;

(现在,我假设您没有在newlib中打开多字节字符串支持MB_CAPABLE。如果你有,事情会变得更加复杂。)其余的看起来很容易调试(strlenmemchr),但是GET_ARG宏可能很复杂——再次取决于您的编译设置(如果您有_NO_POS_ARGS,则简单得多)。

转换后,简单的大小写(格式字符串中没有填充)为:

PRINT (cp, size);

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

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

我恐怕这是有点粘调试,但症状指向的东西在内存中损坏。要检查的一件事是printf的返回值。它应该返回打印的字符数。返回值是否一致有助于调试其余部分。

很抱歉这么晚才帮你解决这个问题。我是valvers.com裸金属教程的作者。坠机的原因是我意识到的,但没有时间解决的问题。事实上,我不知道这会是解决你问题的办法。

简而言之,问题是我们告诉工具链处理器是ARM1176,更重要的是浮点单元是VFP,我们应该使用硬浮点ABI。

使用VFP是一个重要的选项——这意味着我们选择的C库也是用这个选项编译的。通常不使用VFP指令,因此不会使我们出错。显然,printf的一部分确实使用了VFP指令。

这绊倒我们的原因是因为负责生成良好C运行时环境的启动汇编器不启用VFP,所以当你到达VFP指令时,处理器跳转到未定义的指令异常向量。

我就是这样发现问题的。我只是在任何例外向量中启用LED,并且在使用printf格式时它会点亮。然后是移除异常向量中的LED调用,直到它不再亮起。这发生在"未定义指令"异常中。在ARM网站上快速搜索一下就会发现,如果遇到VFP指令而VFP未启用,处理器将转到这里。因此,它提醒我要解决这个问题!

解决方案

有几件事你需要做。你需要在CMakeLists.txt文件中将CMAKE_C_FLAGS复制到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网站)

// 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上进行了测试)。如果您还有别的问题,请尽管问。

最后,很抱歉你因为启动代码不正确而感到悲伤!我最不想做的就是浪费别人的时间!!

相关内容

  • 没有找到相关文章

最新更新