Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: newlib stdio
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Cредства разработки для МК > GNU/OpenSource средства разработки
Spym
Добрый вечер.

Разбираюсь с newlib; сейчас попытался перенаправить стандартный вывод, и столкнулся с такой проблемой:
переопределил функции:
Код
_ssize_t _read_r(struct _reent *r, int file, void *ptr, size_t len);
_ssize_t _write_r(struct _reent *r, int file, const void *ptr, size_t len);

(и прочие низкоуровневые вызовы для newlib)
и вызвал printf(), после чего обнаружил, что _write_r не вызывается.
Попробовал так же вместо указанных функций переопределять лишь
Код
int _read (int file, char *ptr, int len);
int _write(int file,char *ptr,int len);

результат тот же.

Ключи линкера:
Код
LIBS = -lstdc++ -lc -lgcc -lm
LDFLAGS = -nostartfiles -Wl,-Map=$(TARGET).map,--cref $(LIBS) -T$(TARGET).ld


В качестве референса использовал этот проект: http://www.siwawi.arubi.uni-kl.de/avr_proj...ects/#gcc_stdio
Toolchain из последней сборки yagarto.

Итак, вопрос: как переопределить стандартный ввод/вывод, и что я делаю не так?

upd: версия newlib: 1.18
Spym
Попробую издалека:
Поднимите руки, кто использует yagarto и printf.
AHTOXA
Цитата(Spym @ Nov 2 2010, 20:14) *
Ключи линкера:
Код
LIBS = -lstdc++ -lc -lgcc -lm
LDFLAGS = -nostartfiles -Wl,-Map=$(TARGET).map,--cref $(LIBS) -T$(TARGET).ld


Покажите $(TARGET).ld чтольsmile.gif
Я пробовал переопределять
Код
int _write(int file, char *buffer, unsigned int count)

, работало.
Spym
Цитата(AHTOXA @ Nov 2 2010, 22:52) *
Покажите $(TARGET).ld чтольsmile.gif

Запросто.
Нажмите для просмотра прикрепленного файла
AHTOXA
Хм. Не вижу криминала.
Вспоминаю, что читал что-то такое про yagarto... Ага, нашёл:
Цитата
This is because codesourcery build newlib (c library) with
--disable-newlib-supplied-syscalls whereas yagarto builds the default
system calls.

Так что, если это правда, то в yagarto - никак. Берите CodeSourcery, или сборки от klen-а, если есть склонность к экспериментамsmile.gif
Spym
Цитата(AHTOXA @ Nov 2 2010, 23:18) *
Так что, если это правда, то в yagarto - никак.

Цитата(AHTOXA @ Nov 2 2010, 22:52) *
Я пробовал переопределять
Код
int _write(int file, char *buffer, unsigned int count)

, работало.

То есть, вы не с yagarto эксперементировали.
Ок, благодарю за содействие, сейчас посмотрю, какие есть альтернативы.

upd:
Посмотрел Вашу ссылку:
Цитата
Also yagarto is a arm-elf toolchain, and the codesourcery is a arm-eabi
toolchain.

Это же неверное утверждение.

Кстати, тогда вопрос по Yagarto: как там определён стандартный ввод/вывод?
Spym
Прикрутил вместо yagarto/arm-none-eabi сборку kgp-arm-eabi_x86_32_HALLOWEEN_EDITIION от Klen, результат тот же: stdout не перенаправляется.
Я озадачен.
alx2
Меня немного смущает слово "перенаправляется". Что Вы имеете в виду? printf() выполняет вывод в поток stdout (при наличии перечисленных в документации системных вызовов, а именно, close, fstat, isatty, lseek, read, sbrk, write). Таким образом, перенаправление stdout - это просто присваивание ему другого значения. Может у Вас просто в stdout что-то не то?
Покажите, как Вы открываете поток, что присваиваете stdout...
AHTOXA
Цитата(Spym @ Nov 3 2010, 01:29) *
Цитата
Also yagarto is a arm-elf toolchain, and the codesourcery is a arm-eabi
toolchain.

Это же неверное утверждение.

Так год-то прошлый. Сейчас yagarto перешло на arm-eabi, да.

Цитата(Spym @ Nov 3 2010, 03:40) *
Прикрутил вместо yagarto/arm-none-eabi сборку kgp-arm-eabi_x86_32_HALLOWEEN_EDITIION от Klen, результат тот же: stdout не перенаправляется.


Очень странно. Посмотрите ещё вот в этой теме, я там приаттачивал тестовый проект с printf. Он для cortex-m3, но может поможет.

-----
Ещё мысль пришла. Если у вас C++ проект, то функции надо объявлять как
extern "C":

Код
extern "C" int _write(int file, char *buffer, unsigned int count)
{
    for (i=0; i<count; ++i) {
        lcd_put_char(*buffer++);
    }
    return count;
}
Spym
Цитата(alx2 @ Nov 3 2010, 08:55) *
...перенаправление stdout - это просто присваивание ему другого значения. Может у Вас просто в stdout что-то не то?
Покажите, как Вы открываете поток, что присваиваете stdout...

stdout ничего не присваиваю, т.к. достаточно переопределить низкоуровневые функции, которые Вы уже перечислили, разве нет? При входе в main видно, что stdout уже существует (см. ниже).
Цитата(AHTOXA @ Nov 3 2010, 13:09) *
Очень странно. Посмотрите ещё вот в этой теме, я там приаттачивал тестовый проект с printf. Он для cortex-m3, но может поможет.

Посмотрел, принципиальных отличий не нашел.
Цитата(AHTOXA @ Nov 3 2010, 13:09) *
Ещё мысль пришла. Если у вас C++ проект, то функции надо объявлять как extern "C":

Эти функции определены в syscalls.c, всё в порядке.
Да, я забыл упомянуть, что при вызове printf управление всё же передавалось в:
Код
int _fstat_r(struct _reent *r, int file, struct stat *st);
int _isatty_r(struct _reent *r, int file);

Но вместо _write_r затем уходило в __swrite.

Попробовал сейчас перед вызовом printf() руками определить указатель на _write_r():
Код
stdout->_write = reinterpret_cast<typeof(stdout->_write)>(_write_r);

Пока я этого не сделал, stdout->_write указывал на NULL.
Вызвал printf(), смотрю значение stdout->_write - теперь указывает на __swrite.
Spym
Я прозрел; как выяснилось, вывод корректно работает даже в Yagarto.

Из первого вызова printf вызывается __sinit, где происходит инициализация stdout/stdin/stderr. После этого stdout->_write указывает на __swrite, откуда как раз и происходит вызов _write_r, а отсюда, в свою очередь, вызывется пользовательский _write; но и _write_r можно переопределить.

Моя проблема заключалась в том, что вызов printf, где строка формата не содержит управляющих последовательностей:
Код
printf("Something string");

в newlib не приводит к выводу этой строки в stdout; после замены на:
Код
printf("%s", "Just string\r\n");

все ВНЕЗАПНО заработало.
Почему так? Ведь в этом случае в stdout должно было уйти "Something string".
AHTOXA
Цитата(Spym @ Nov 3 2010, 19:53) *
Я прозрел; как выяснилось, вывод корректно работает даже в Yagarto.

Это радует, значит Yagarto потихоньку движется в сторону, так сказать, общепринятых тенденций. И eabi, и --disable-newlib-supplied-syscalls. Больше тулзов, хороших и разных! smile.gif
Цитата
Почему так?

Мне кажется, что это кривизна newlib. Хотя неясно, чем отличается _write из вашего файла syscalls.c от _write дефолтного.
r301
Цитата(Spym @ Nov 3 2010, 17:53) *
Почему так? Ведь в этом случае в stdout должно было уйти "Something string".


обычная буферизация. попробуйте так
Код
printf("Something string");
fflush(stdout);

klen
Цитата(Spym @ Nov 3 2010, 01:40) *
Прикрутил вместо yagarto/arm-none-eabi сборку kgp-arm-eabi_x86_32_HALLOWEEN_EDITIION от Klen, результат тот же: stdout не перенаправляется.
Я озадачен.

давайте прикрутим вместе. но после праздникрв пролетариата - я уже пикирую.
не думаю что есть проблемы где есть исходный код smile.gif

всех форумчан с празгиком, история нашего народа однако...
Spym
Цитата(r301 @ Nov 3 2010, 21:37) *
обычная буферизация. попробуйте так
Код
printf("Something string");
fflush(stdout);

Действительно.
Интересно, как я могу управлять размером буфера?
И почему использование управляющих последовательностей не приводит к буферизации вывода?

Цитата(klen @ Nov 3 2010, 22:00) *
давайте прикрутим вместе.

В прикручивании как раз проблем не возникло - достаточно указать в PATH путь к toolchain же.
Да, кстати, при -O0 ваш KGP собрал 160кБ кода против 202кБ -O0 yagarto (-lc -lm -lgcc -lstdc++) при прочих равных, на первый взгляд всё работало. Хотя это сейчас мало о чем говорит, да и полученный код я не тестировал пока толком.
Сергей Борщ
Цитата(Spym @ Nov 4 2010, 14:51) *
Интересно, как я могу управлять размером буфера?
Посмотрите setvbuf();

Petka
Цитата(Spym @ Nov 4 2010, 14:51) *
...
И почему использование управляющих последовательностей не приводит к буферизации вывода?
...

newlib - достаточно "взрослая" библиотека. Для увеличения производительности она буферизует вывод, в том числе и на stdout (насколько я помню stderr не буферизуется для удобства отладки). Содержимое буфера скидывается либо по fflush как было сказано выше, либо по символу перевода строки "\r\n".
По поводу yagartoo - она уже давно умела нормально собирать данный функционал.
Spym
Цитата(Сергей Борщ @ Nov 4 2010, 15:39) *
Посмотрите setvbuf();

Цитата(Petka @ Nov 4 2010, 16:00) *
newlib - достаточно "взрослая" библиотека. Для увеличения производительности она буферизует вывод, в том числе и на stdout (насколько я помню stderr не буферизуется для удобства отладки). Содержимое буфера скидывается либо по fflush как было сказано выше, либо по символу перевода строки "\r\n".
По поводу yagartoo - она уже давно умела нормально собирать данный функционал.

Благодарю, уже почитал маны по теме. Не ожидал, что embedded библиотека столь "достаточно "взрослая"".
alx2
Цитата(Spym @ Nov 3 2010, 19:53) *
Из первого вызова printf вызывается __sinit, где происходит инициализация stdout/stdin/stderr.

Вот этот момент мне не очень понятен. Чем инициализируется stdout? Иными словами, куда, на какое устройство будут выводиться данные, отправляемые в stdout?

У меня типичная инициализация происходит так (упрощенный пример - сервер, принимающий tcp соединения):
Код
.....
int s = socket(.....);
listen(s, .....);
.....
int sd = accept(s);  // получили дескриптор нового tcp соединения
stdin = stdout = stderr = fdopen(sd, "w+");
.............
printf("Hello!\n");

В результате вызова printf() я получаю вызов _write(int, char*, int), первым аргументом которого будет значение sd, указанное при вызове fdopen().
А что я получу в качестве первого аргумента _write, если не сделаю fdopen? Как в этом случае _write должна решить, куда отправлять данные, на которые указывает второй аргумент?...
Spym
Цитата(alx2 @ Nov 6 2010, 23:36) *
Вот этот момент мне не очень понятен. Чем инициализируется stdout?

Как я уже писал, на момент входа в main дескрипторы для stdin/stdout/stderr уже существуют, и инициализируются при первом обращении при помощи __sinit.

Цитата(alx2 @ Nov 6 2010, 23:36) *
В результате вызова printf() я получаю вызов _write(int, char*, int), первым аргументом которого будет значение sd, указанное при вызове fdopen().
А что я получу в качестве первого аргумента _write, если не сделаю fdopen? Как в этом случае _write должна решить, куда отправлять данные, на которые указывает второй аргумент?...

В случае newlib, вы получите дескриптор _reent->_stdin (ну или _stdout, _stderr) (посмотрите в newlib: sys/reent.h) - это позволит принять решение о целевом устройстве.
alx2
Цитата(Spym @ Nov 7 2010, 09:55) *
Как я уже писал, на момент входа в main дескрипторы для stdin/stdout/stderr уже существуют, и инициализируются при первом обращении при помощи __sinit.
В случае newlib, вы получите дескриптор _reent->_stdin (ну или _stdout, _stderr) (посмотрите в newlib: sys/reent.h) - это позволит принять решение о целевом устройстве.
Не могу с Вами полностью согласиться.

Стандартные потоки, безусловно, существуют, как некий объект в памяти. Вопрос был в том, можно ли начать работать с этими объектами, предварительно явно их не инициализировав. О том, что они автоматически инициализируются при первом обращении, я не знал, спасибо за информацию. Мысль же моя заключалась в том, что для последующей работы с выводом на низком уровне (в соответствующих системных вызовах) необходимо, чтобы этим системным вызовам передавались осмысленные аргументы и, в частности, файловый дескриптор. При явной инициализации потока (через fopen или fdopen) это осмысленное значение предоставляет программист. А если инициализация происходит "автомагически", без участия программиста, откуда тогда возьмется это осмысленное значение?

Вот сейчас я посмотрел код newlib. Значение дескриптора хранится в поле _file структуры FILE. То есть системным вызовам, в нашем случае _write(), в качестве первого аргумента передается _reent->_stdout->_file. Теперь смотрим, какое же значение там находится. __sinit() для инициализации стандартных потоков вызывает __sfp(), которая, в свою очередь, инициализирует поле _file значением -1. Таким образом, при выполнении каких-либо операций с таким потоком системным вызовам будет передаваться -1 в качсетве файлового дескриптора, что традиционно означает невалидный дескриптор, и такой системный вызов, как правило, должен давать ошибку EBADF (bad file descriptor). По крайней мере, никакого решения о целевом устройстве такой дескриптор принять не позволит.

Конечно, если в целевой системе заведомо существует только одно устройство ввода/вывода (например один com-порт), тогда все операции производятся с ним и только с ним, и можно, наверное, в системных вызовах не анализировать значение дескриптора. Но как только мы допускаем наличия нескольких логических устройств (хотя бы двух com-портов), возникает и необходимость мультиплексировать операции ввода/вывода, а для этого потребуется селектор, которым и является дескриптор, передаваемый первым аргументом. И тогда придется-таки открывать потоки явно через fopen(3)/fdopen(3)...
Spym
Цитата(alx2 @ Nov 7 2010, 23:53) *
Не могу с Вами полностью согласиться.

Подозреваю, что мы говорим о различных версиях newlib; у меня 1.18, как я уже писал тут.

Цитата(alx2 @ Nov 7 2010, 23:53) *
если инициализация происходит "автомагически", без участия программиста, откуда тогда возьмется это осмысленное значение?

В случае 1.18, значение возмется из machine/spu/c99ppe.h:
Код
#define SPE_STDIN                   1
#define SPE_STDOUT                  2
#define SPE_STDERR                  3


Цитата(alx2 @ Nov 7 2010, 23:53) *
__sinit() для инициализации стандартных потоков вызывает __sfp(), которая, в свою очередь, инициализирует поле _file значением -1.

Для 1.18 это не совсем верно:
Код
_VOID
_DEFUN (__sinit, (s),
    struct _reent *s)
{
  s->__cleanup = __cleanup;
  s->__sdidinit = 1;

  s->_stdin = &s->__sf[0];
  s->_stdin->_fp = SPE_STDIN;

  s->_stdout = &s->__sf[1];
  s->_stdout->_fp = SPE_STDOUT;

  s->_stderr = &s->__sf[2];
  s->_stderr->_fp = SPE_STDERR;
}

Соответственно, для stdin/stdout/stderr мы получим дескрипторы 1, 2, 3.
alx2
А, понял. Вы говорите о __sinit из newlib/libc/machine/spu/stdio.c, а я смотрел __sinit() из newlib/libc/stdio/findfp.c.
У меня (вот сейчас смотрю map-файл реального проекта) используется __sinit именно из findfp.o (arm-elf/lib/libc.a). А у Вас не так?

Цитата(Spym @ Nov 8 2010, 13:03) *
Соответственно, для stdin/stdout/stderr мы получим дескрипторы 1, 2, 3.
Вы говорите о различиях стандартных потоков ввода, вывода и ошибок. Я говорил о другом.
Допустим, в целевом устройстве два com-порта. Для работы с ними я запускаю две одинаковые задачи. В каждой из них выполняется printf(), то есть производится вывод в stdout. Но одна задача должна выводить в один com-порт, другая - в другой! Для этого каждая задача должна по-своему проинициализировать stdout. Если первая задача выполнит stdout = fdopen(1, "w+"); а вторая - stdout = fdopen(2, "w+");, то printf(...) из первой задачи даст вызов _write(1, ....), а из второй - _write(2, .....).
Spym
Вы не ответили, какую версию newlib используете.

Цитата(alx2 @ Nov 9 2010, 10:14) *
Вы говорите о различиях стандартных потоков ввода, вывода и ошибок. Я говорил о другом.

Верно, я говорил о другом, априори полагая, что все таски должны всё-таки по-умолчанию использовать общий stdout.
alx2
Цитата(Spym @ Nov 9 2010, 14:17) *
Вы не ответили, какую версию newlib используете.
А Вы не спрашивали. smile.gif Вы лишь высказали предположение, что мы говорим о разных версиях...
Нет, я говорю о newlib-1.18.0.
Spym
Цитата(alx2 @ Nov 10 2010, 09:24) *
А Вы не спрашивали. smile.gif

Точно - я вопрос забыл написать, но он подразумевался. smile.gif
Ну и наконец, значения дескрипторов при выполнении:
Цитата
"_impure_ptr->_stdin->_file" short int 0 short int
"_impure_ptr->_stdout->_file" short int 1 short int
"_impure_ptr->_stderr->_file" short int 2 short int

Хм.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.