挂起应用调用的printf时裸机树莓派至UART [英] Application hangs when calling printf to uart with bare metal raspberry pi

查看:404
本文介绍了挂起应用调用的printf时裸机树莓派至UART的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想实现的树莓PI裸机应用程序,并希望到stdout挂接到迷你UART进行调试。

我按照列出的这里和的这里

我创建了一个uart_putc功能,这似乎很好地工作,让我消息打印到我的电脑的COM口。然后,我实现了_write系统调用,使得它叫我的输出uart_putc功能。如果我通过一个字符串到printf的附加的文字参数或没有打印到串行端口后打了几个电话的任何非文字参数,应用程序挂起这工作得很好。

有没有人有任何想法什么可能会错了吗?乐意为您提供进一步的信息,如果需要的...

 无效uart_putc(焦三)
{
    而(1)
    {
        如果(AUX [AUX_MU_LSR&安培;为0x20)打破;        led_blink(); //使LED闪烁关闭然后再
                     //确保我们不会卡在这里
    }
    AUX [AUX_MU_IO] = C;
}

...

  INT _write(INT文件,字符* PTR,INT LEN)
{
    INT待办事项;    对于(待办事项= 0;&待办事项LT; LEN,待办事项++)
    {
        uart_putc(* PTR ++);
    }
    返回LEN;
}

...

 而(1)
{
    的printf(你好世界\\ r \\ n); //作品
}而(1)
{
    的printf(%s您好\\ r \\ n,世界); //这不打印任何东西
                                     //并会后约五挂电话
}字符* S =(字符*)malloc的(的sizeof(字符)* 100); //堆或堆栈没关系
的strcpy(S,的Hello World \\ r \\ n);
而(1)
{
    的printf(S); //这不打印任何东西
               //并会后约五挂电话
}而(1)
{
    对于(I = 0; I&; 13;我+ +)
    {
        uart_putc(S [I]); //作品
    }
}

更新

我使用newlib时直接调用_write正常工作。的snprintf似乎表现出了同样的问题,即

 的snprintf(S,100的hello world \\ r \\ n); //作品
的snprintf(S,100,喂%S \\ r \\ n,世界); //不工作

我_sbrk的实现是从我的OP引用的页面缺口

 的char * heap_end = 0;
采用caddr_t _sbrk(INT增量){
    焦炭的extern heap_low; / *链接器定义* /
    焦炭的extern heap_top; / *链接器定义* /
    字符* prev_heap_end;    如果(heap_end == 0)
    {
        heap_end =安培; heap_low;
    }
    prev_heap_end = heap_end;    如果(heap_end +增量>&安培; heap_top)
    {
        / *堆和栈碰撞* /
        回报(采用caddr_t)0;
    }    heap_end + =增量;
    返回(采用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目录);
截面
{
  / *只读部分,合并为文本段:* /
  提供:(__executable_start = SEGMENT_START(文本段,为0x8000)); 。 = SEGMENT_START(文本段,为0x8000);
。 = 0x8000的;
 .RO:{
  *(text.startup)
  *(。文本)
  *(rodata)
 }
 .rw:{
  *(。数据)
  __bss_start__ =。
  *(BSS)
  __bss_end__ =。
  *(共同)
 }
 。 = ALIGN(8);
 heap_low =。 / *为_sbrk * /
 。 =。 + 0x10000的; / *堆内存64kB的* /
 heap_top =。 / *为_sbrk * /
 。 =。 + 0x10000的; / *堆栈内存64kB的* /
 stack_top =。 / *为*的Startup.s /
}

的start.s

  .section伪.text.startup。全球_start_开始:
    LDR SP,= stack_top    //在C-启动
    b _cstartup_inf_loop:
    b _inf_loop

更新2

涉及的snprintf进一步的实验:

 的snprintf(S,100的hello world \\ r \\ n); //作品的snprintf(S,100,喂%S \\ r \\ n,世界); //不工作
的snprintf(S,100,喂%d个\\ r \\ n,1); //不工作个char [100];
烧焦T [100];的strcpy(S,世界你好\\ r \\ n);
的snprintf(T,100,S); //不工作


解决方案

这看起来并不像一个UART问题,而是一个库的问题。

如果你想确保我的假设是正确的,调用 _write()直接看到的,如果它的工作原理。最有可能它会。另外,我假设你使用 newlib

如果 _write()作品如预期,问题是有限的成上层的printf 。不幸的是的printf 就像一个洋葱,你必须削皮层 - 层,它会让你哭的。

只是为了好玩,从 newlib 源$ C ​​$ C的代码段:

  / *
 *实际printf的内脏。
 *
 *此code是大而复杂...
 * /

幸运的是,还是有一些方法来调试问题,而不迷路到 vfprintf.c 。也许最简单的出发点是尽量的snprintf(),因为它缺少内存管理的问题。内存分配code包括诸如 SBRK 这可能是一个问题。人们会被诱惑认为内存管理是好的,因为的malloc()看似有效,但事实并非总是如此。 (的malloc()看起来OK即使它给出错误的地址,但内存损坏会发生的。)

让我们知道你在哪里使用这些调试步骤搞定! (我的猜测是, SBRK 由于某种原因不能正常工作,而沉船的内存管理。)


更新,因为它似乎,这个问题是不是在内存分配 - 至少不仅在内存分配 - 我们需要解决的洋葱。我希望你不是穿着过于浓妆艳抹...(这让我哭了,我不是100%肯定,下面的分析是正确的,所以把它用少许盐。)

newlib 会发生什么事的printf 被称为?这个故事是在文件夹中的 newlib newlib /的libc / stidio

第1层:的printf()

首先, printf.c

  INT
_DEFUN(的printf(FMT)
       为const char * __限制FMT _DOTS)
{
  INT RET;
  va_list的AP;
  结构_reent * PTR = _REENT;  _REENT_SMALL_CHECK_INIT(PTR);
  的va_start(AP,FMT);
  RET = _vfprintf_r(PTR,_stdout_r(PTR),格式化,AP);
  va_end用来(AP);
  返回RET;
}

这很简单。如果事情错了,它可以是:


  • _REENT_SMALL_CHECK_INIT(PTR);

  • 可变参数的处理

我不认为重入是这里的问题,所以我会集中在可变参数。也许这将是一个好主意,以最小的可变参数测试code,那么这将表明,如果他们都断了。 (我不明白为什么他们会被打破,但在嵌入式系统中是比较安全的,不要认为任何事情。)

2层: _vfprintf_r()

这是标准的 vfprintf 的内部版本 - 与折返(文件的可变参数版本的printf ) code。它在定义vfprintf.c 。它有几种类型,这取决于交换机的库编译过程中使用: STRING_ONLY (无内存分配)和/或 NO_FLOATING_POINT 。我假设你有完整的版本,在这种情况下,正确的功能,可以在名称中找到 _VFPRINTF_R (有些的#define ING已经持续)。

在code是不是太容易阅读,首先几百行的函数声明包含许多变量(取决于编译选项),十几宏。然而,第一件事功能确实是一个无限循环扫描格式字符串。当它找到一个 \\ 0 相反,它转到做; (是的,它有转到 S,以及 - 我想抛出此为code评价......)

然而,这给了我们一个线索:如果我们不把任何额外的参数,我们就跳到完成,其中我们有一些清理工作。由此我们可以生存,但任何格式的参数的处理。那么,让我们来看看其中,%S 将结束。这样做是为人们所期望的,有一个大的开关(CH)... 。在取值它说:

 情况下,'S':
        CP = GET_ARG(N,AP,char_ptr_t);
        签='\\ 0';
        如果(preC> = 0){
            / *
             *不能使用strlen;只能看的
             * NUL第一`$ P $电脑字符,
             * strlen的()会走得更远。
             * /
            的char * p =了memchr(CP,0,preC);            如果(P!= NULL){
                大小= P - CP;
                如果(大小> preC)
                    大小= preC;
            }其他
                大小= preC;
        }其他
            大小= strlen的(CP);        打破;

(现在,我假设你没有多字节字符串支持 MB_CAPABLE newlib 。如果开机你拥有的东西刚要复杂得多。),其余看起来很容易调试(的strlen 了memchr ),但 GET_ARG 宏可能是复杂的 - 这也取决于你的编译设置(如果你有 _NO_POS_ARGS ,这是很简单)。

在开关,简单的情况(格式字符串没有填充)是:

  PRINT(CP,大小);

这是印刷宏。至少如果指针 CP 是错的,那么奇怪的事情会发生。

宏本身不能是疯狂的窘况,因为我们可以打印的简单情况;唯一的参数会导致问题。

这恐怕是有点粘调试,但症状指向东西在内存越来越损坏。有一件事是检查你的的printf的的返回值。它应该返回打印的字符数。无论是否返回值是理智的帮助调试的其余部分。

I am trying to implement a bare metal application on the raspberry pi and want to hook up stdout to the mini uart for debugging purposes.

I have followed the process outlined here and here

I have created a uart_putc function which seems to work perfectly, allowing me to print messages to my PC's COM port. I then implemented the _write syscall, making it call my uart_putc function for output. This works fine if I pass a single string literal into printf additional literal parameters or any non-literal parameters nothing is printed to the serial port and after a few calls, the application hangs.

Does anyone have any ideas what might be going wrong? Happy to provide further info if needed...

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
    }
}

Update

I am using newlib and _write works correctly when called directly. snprintf seems to exhibit the same problem, i.e.

snprintf(s, 100, "hello world\r\n"); // Works
snprintf(s, 100, "hello %s\r\n", "world"); // Doesn't work

My implementation of _sbrk was nicked from the page referenced in my 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;
 }

Linker script

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

Update 2

Further experiments involving 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

解决方案

This does not look like a UART problem, but rather a library problem.

If you want to make sure my assumption is correct, call _write() directly and see, if it works. Most probably it will. Also, I assume you are using newlib.

If _write() works as expected, the problem is limited into the upper layers of printf. Unfortunately printf is like an onion, you have to peel it layer-by-layer, and it'll make you cry.

Just for fun, a snippet from the newlib source code:

/*
 * Actual printf innards.
 *
 * This code is large and complicated...
 */

Luckily, there are still some ways to debug the problem without getting lost into vfprintf.c. Maybe the easiest starting point is to try snprintf(), because it lacks the memory management problems. The memory allocation code includes things such as sbrk which may be one problem. One would be tempted to think that the memory management is ok, as malloc() seemingly works, but that is not always the case. (malloc() may look ok even if it gives wrong addresses, but memory corruption will happen.)

Let us know where you get with these debugging steps! (My educated guess is that sbrk does not work for some reason, and that wrecks memory management.)


Update As it seems that the problem is not in memory allocation -- at least not only in memory allocation -- we need to tackle the onion. I hope you are not wearing too heavy make-up... (This makes me cry, and I am not 100 % sure the analysis below is correct. So take it with a pinch of salt.)

What happens in newlib when printf is called? The story is in the newlib source in folder newlib/libc/stidio.

Layer 1: printf()

First, 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;
}

Quite simple. If something is going wrong, it is either:

  • _REENT_SMALL_CHECK_INIT(ptr); or
  • handling of varargs

I don't think the re-entrancy is a problem here, so I would concentrate on the varargs. Maybe it would be a good idea to make a minimal varargs test code, which would then show if they are broken. (I do not see why they would be broken, but in an embedded system it is safer not to assume anything.)

Layer 2: _vfprintf_r()

This is an internal version of standard vfprintf (varargs-version of file-printf) with re-entrant code. It is defined in vfprintf.c. It comes in several flavours depending on which switches have been used during the library compilation: STRING_ONLY (no memory allocation) and/or NO_FLOATING_POINT. I'll assume you have the full version, in which case the correct function can be found under the name _VFPRINTF_R (some #defineing has been going on).

The code is not too easy to read, first few hundred lines of the function consist of declaring many variables (depending on the compile options) and a dozen or so macros. However, the first thing the function really does is an endless loop to scan the format string for %. When it finds a \0 instead, it does goto done; (yeah, it's got gotos, as well -- I'd like to throw this to code review...)

However, this gives us a clue: if we do not put any extra arguments, we just jump to done where we have some cleanup. This we can survive, but not the handling of any format arguments. So, let us look at where %s would end up. This is done as one would expect, there is a big switch(ch) .... At s it says:

    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;

(Now, I have assumed you do not have multibyte string support MB_CAPABLE switched on in your newlib. If you have, the thing just got much more complicated.) The rest looks easy to debug (strlen and memchr), but the GET_ARGmacro may be complicated -- again depending on your compile settings (if you have _NO_POS_ARGS, it is much simpler).

After the switch, the simple case (no padding in the format string) is:

PRINT (cp, size);

which is the printing macro. At least if the pointer cp is wrong, then odd things will happen.

The macro itself cannot be horrendously crazy, as we can print the simple case; only the arguments cause problems.

I am afraid this is a bit sticky to debug, but the symptoms point at something getting corrupted in memory. One thing to check is the return value of your printf. It should return the number of characters printed. Whether or not the return value is sane helps debugging the rest.

这篇关于挂起应用调用的printf时裸机树莓派至UART的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆