Розфарбувати введення користувача складно, оскільки в половині випадків він виводиться драйвером терміналу (з локальним відлунням), тому в цьому випадку жодна програма, що працює в цьому терміналі, може не знати, коли користувач збирається вводити текст і відповідно змінювати колір виводу. . Тільки драйвер псевдотерміналу (в ядрі) знає (емулятор термінала (як xterm) надсилає йому деякі символи під час натискання клавіші, і драйвер терміналу може надсилати назад кілька символів для ехо, але xterm не може знати, чи є вони з локальне відлуння або від того, що додаток виводить на підлеглий бік псевдотерміналу).
І тоді, є інший режим, коли драйверу терміналу кажуть, щоб не лунало, але додаток цього разу щось виводить. Додаток (як, наприклад, такі, що використовують readline, як gdb, bash ...), може надіслати це в його stdout або stderr, що буде важко відрізнити від чогось, що виводить для інших речей, ніж повторення даних користувача.
Тоді для того, щоб відрізнити stdout програми від його stderr, існує кілька підходів.
Багато з них передбачають перенаправлення команд stdout та stderr на труби, а ті труби, які читаються програмою для її забарвлення. З цим є дві проблеми:
- Після того, як stdout більше не є терміналом (як натомість труба), багато додатків прагнуть адаптувати свою поведінку, щоб почати буферизацію свого виходу, що означає, що вихід буде відображатися великими шматками.
- Навіть якщо це той самий процес, який обробляє дві труби, немає гарантії, що впорядкованість тексту, написаного додатком на stdout та stderr, буде збережена, оскільки процес читання не може знати (якщо з обох є що читати) чи слід починати читання з труби "stdout" або "stderr".
Інший підхід полягає в тому, щоб змінити додаток таким чином, щоб воно забарвлювало його stdout та stdin. Це часто неможливо чи реально зробити.
Тоді хитрістю (для динамічно пов'язаних додатків) може бути викрадення (використовуючи $LD_PRELOADяк у відповіді на серп ) функції виводу, викликані програмою, щоб щось вивести і включити в них код, який встановлює колір переднього плану, залежно від того, чи призначені вони для того, щоб щось вивести на stderr або stdout. Однак це означає викрадення будь-якої можливої функції з бібліотеки С та будь-якої іншої бібліотеки, яка виконує write(2)системний виклик, безпосередньо викликаний програмою, що потенційно може написати щось на stdout або stderr (printf, put, perror ...), і навіть тоді , що може змінити його поведінку.
Іншим підходом може бути використання прийомів PTRACE як straceабо gdbробити, щоб підключити себе щоразу, коли write(2)системний виклик викликається та встановити колір виходу на основі того, чи write(2)є в дескрипторі файлу 1 чи 2.
Однак це досить велика річ.
Трюк, з яким я щойно грав, - це викрасти straceсебе (що робить брудну роботу підключення до кожного системного виклику) за допомогою LD_PRELOAD, щоб сказати йому змінити вихідний колір на основі того, чи виявив він write(2)на fd 1 або 2.
Переглядаючи straceвихідний код, ми бачимо, що все, що він виводиться, здійснюється за допомогою vfprintfфункції. Все, що нам потрібно зробити, - це викрасти цю функцію.
Обгортка LD_PRELOAD виглядатиме так:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
int vfprintf(FILE *outf, const char *fmt, va_list ap)
{
static int (*orig_vfprintf) (FILE*, const char *, va_list) = 0;
static int c = 0;
va_list ap_orig;
va_copy(ap_orig, ap);
if (!orig_vfprintf) {
orig_vfprintf = (int (*) (FILE*, const char *, va_list))
dlsym (RTLD_NEXT, "vfprintf");
}
if (strcmp(fmt, "%ld, ") == 0) {
int fd = va_arg(ap, long);
switch (fd) {
case 2:
write(2, "\e[31m", 5);
c = 1;
break;
case 1:
write(2, "\e[32m", 5);
c = 1;
break;
}
} else if (strcmp(fmt, ") ") == 0) {
if (c) write(2, "\e[m", 3);
c = 0;
}
return orig_vfprintf(outf, fmt, ap_orig);
}
Потім ми компілюємо його з:
cc -Wall -fpic -shared -o wrap.so wrap.c -ldl
І використовувати його як:
LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd
Ви помітите , як якщо замінити some-cmdз bash, на БАШЕЄВ рядку і що ви друкуєте в той час як з з'являється в червоному (STDERR) zshз'являється в чорному (бо ЗШ Dups стандартного висновок на новий дескриптор , щоб відобразити його швидке і відлуння).
Схоже, це працює напрочуд добре навіть для програм, яких ви не очікуєте (як, наприклад, для кольорів).
Режим забарвлення виводиться на stracesderder, який передбачається терміналом. Якщо програма переспрямовує stdout або stderr, наша викрадена страйка продовжуватиме писати послідовності втечі розмальовки на терміналі.
Це рішення має свої обмеження:
- Ті, які притаманні
strace: проблемам із продуктивністю, ви не можете запускати інші команди PTRACE, як-от straceабо gdbв ній, або встановлені / встановлені жорсткі проблеми
- Це забарвлення на основі
writes на stdout / stderr кожного окремого процесу. Так, наприклад, в sh -c 'echo error >&2', errorвін буде зеленим, тому що echoвиводить його на свій stdout (який sh перенаправлений на stderr sh, але всі стрази бачать a write(1, "error\n", 6)). І в sh -c 'seq 1000000 | wc', seqробить багато або writeз його stdout, так що обгортка в кінцевому підсумку виведе багато (невидимих) послідовностей втечі до терміналу.