Cómo generar automáticamente un stacktrace cuando mi programa falla

17 minutos de lectura

Como generar automaticamente un stacktrace cuando mi programa falla
KPexEA

Estoy trabajando en Linux con el compilador GCC. Cuando mi programa C++ falla, me gustaría que generara automáticamente un seguimiento de pila.

Mi programa está siendo ejecutado por muchos usuarios diferentes y también se ejecuta en Linux, Windows y Macintosh (todas las versiones se compilan usando gcc).

Me gustaría que mi programa pueda generar un seguimiento de la pila cuando falla y la próxima vez que el usuario lo ejecute, les preguntará si está bien enviarme el seguimiento de la pila para que pueda localizar el problema. Puedo encargarme de enviarme la información, pero no sé cómo generar la cadena de seguimiento. ¿Algunas ideas?

  • backtrace y backtrace_symbols_fd no son seguros para señales asíncronas. no debe usar estas funciones en el controlador de señales

    – ParagBafna

    14 de junio de 2012 a las 9:08

  • backtrace_symbols llama a malloc, por lo que no debe usarse en un controlador de señales. Las otras dos funciones (backtrace y backtrace_symbols_fd) no tienen este problema y se usan comúnmente en los controladores de señales.

    – cmcabe

    02/08/2012 a las 20:01

  • @cmccabe que es incorrecto backtrace_symbols_fd generalmente no llama a malloc pero puede hacerlo si algo sale mal en su bloque catch_error

    – Sam Azafrán

    17 de diciembre de 2013 a las 22:24

  • “Puede” en el sentido de que no hay una especificación POSIX para backtrace_symbols_fd (o cualquier backtrace); sin embargo, se especifica que backtrace_symbols_fd de GNU/Linux nunca llame a malloc, según linux.die.net/man/3/backtrace_symbols_fd . Por lo tanto, es seguro asumir que nunca llamará a malloc en Linux.

    – codetaku

    17 de julio de 2014 a las 14:42

  • ¿Cómo se bloquea?

    – Ciro Santilli Путлер Капут 六四事

    19/03/2017 a las 12:00

1647571994 52 Como generar automaticamente un stacktrace cuando mi programa falla
Todd Gamblin

Para Linux y creo que Mac OS X, si está usando gcc, o cualquier compilador que use glibc, puede usar las funciones backtrace() en execinfo.h para imprimir un stacktrace y salir con gracia cuando obtenga una falla de segmentación. La documentación se puede encontrar en el manual de libc.

He aquí un programa de ejemplo que instala un SIGSEGV handler e imprime un stacktrace para stderr cuando falla. los baz() La función aquí provoca el error de segmento que activa el controlador:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

Compilando con -g -rdynamic obtiene información del símbolo en su salida, que glibc puede usar para hacer un seguimiento de pila agradable:

$ gcc -g -rdynamic ./test.c -o test

Ejecutar esto te da esta salida:

$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]

Esto muestra el módulo de carga, el desplazamiento y la función de la que procede cada fotograma de la pila. Aquí puede ver el controlador de señal en la parte superior de la pila y las funciones libc antes main además de main, foo, bary baz.

  • También está /lib/libSegFault.así que puede usar con LD_PRELOAD.

    – CesarB

    23 de octubre de 2008 a las 15:05

  • Parece que las dos primeras entradas en su salida de seguimiento contienen una dirección de retorno dentro del controlador de señal y probablemente una dentro sigaction() en lib. Si bien su rastreo inverso parece ser correcto, a veces he descubierto que se necesitan pasos adicionales para garantizar que la ubicación real de la falla aparezca en el rastreo inverso, ya que se puede sobrescribir con sigaction() por el núcleo.

    – jschmier

    27 de marzo de 2010 a las 19:11

  • ¿Qué pasaría si el choque viene del interior de malloc? ¿No mantendría entonces un candado y luego se quedaría atascado cuando el “retroceso” intente asignar memoria?

    –Mattias Nilsson

    17 de abril de 2012 a las 6:39

  • catchsegv no es lo que necesita el OP, pero es increíble para detectar fallas de segmentación y obtener toda la información.

    –Matt Clarkson

    30 de enero de 2013 a las 10:45

  • Para ARM, también tuve que compilar con -funwind-tables. De lo contrario, mi profundidad de pila siempre fue 1 (vacío).

    – jfritz42

    10/04/2013 a las 20:17

1647571994 94 Como generar automaticamente un stacktrace cuando mi programa falla
jhclark

Es incluso más fácil que “man backtrace”, hay una biblioteca poco documentada (específica de GNU) distribuida con glibc como libSegFault.so, que creo que fue escrita por Ulrich Drepper para admitir el programa catchsegv (ver “man catchsegv”).

Esto nos da 3 posibilidades. En lugar de ejecutar “programa -o hai”:

  1. Ejecutar dentro de catchsegv:

    $ catchsegv program -o hai
    
  2. Enlace con libSegFault en tiempo de ejecución:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Enlace con libSegFault en tiempo de compilación:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
    

En los 3 casos, obtendrá trazas inversas más claras con menos optimización (gcc -O0 o -O1) y símbolos de depuración (gcc -g). De lo contrario, puede terminar con un montón de direcciones de memoria.

También puede capturar más señales para seguimientos de pila con algo como:

$ export SEGFAULT_SIGNALS="all"       # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT

La salida se verá así (observe el seguimiento en la parte inferior):

*** Segmentation fault Register dump:

 EAX: 0000000c   EBX: 00000080   ECX:
00000000   EDX: 0000000c  ESI:
bfdbf080   EDI: 080497e0   EBP:
bfdbee38   ESP: bfdbee20

 EIP: 0805640f   EFLAGS: 00010282

 CS: 0073   DS: 007b   ES: 007b   FS:
0000   GS: 0033   SS: 007b

 Trap: 0000000e   Error: 00000004  
OldMask: 00000000  ESP/signal:
bfdbee20   CR2: 00000024

 FPUCW: ffff037f   FPUSW: ffff0000  
TAG: ffffffff  IPOFF: 00000000  
CSSEL: 0000   DATAOFF: 00000000  
DATASEL: 0000

 ST(0) 0000 0000000000000000   ST(1)
0000 0000000000000000  ST(2) 0000
0000000000000000   ST(3) 0000
0000000000000000  ST(4) 0000
0000000000000000   ST(5) 0000
0000000000000000  ST(6) 0000
0000000000000000   ST(7) 0000
0000000000000000

Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]

Si desea conocer los detalles sangrientos, la mejor fuente es, lamentablemente, la fuente: consulte http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c y su directorio padre http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

  • “Posibilidad 3. Enlace con libSegFault en tiempo de compilación” no funciona.

    – HHK

    23 de enero de 2013 a las 18:05

  • @crafter: ¿Qué quieres decir con “no funciona”. ¿Qué ha intentado, en qué idioma/compilador/cadena de herramientas/distribución/hardware? ¿Falló al compilar? Para atrapar el error? ¿Para producir salida en absoluto? ¿Para producir resultados difíciles de usar? Gracias por los detalles que ayudará a todos.

    – Stéphane Gourichon

    31 de marzo de 2014 a las 9:33

  • ‘la mejor fuente es, lamentablemente, la fuente’… Con suerte, algún día, la página de manual de catchsegv mencionará SEGFAULT_SIGNALS. Hasta entonces, hay esta respuesta a la que referirse.

    – grego

    3 de julio de 2014 a las 16:06


  • No puedo creer que he estado programando C durante 5 años y nunca he oído hablar de esto :/

    – DavidMFrey

    16 de marzo de 2016 a las 12:44

  • @StéphaneGourichon @HansKratz Para enlazar con libSegFault tendrás que agregar -Wl,--no-as-needed a las banderas del compilador. De lo contrario, ld de hecho no enlace contra libSegFaultporque reconoce que el binario no usa ninguno de sus símbolos.

    – Felipe

    28 de julio de 2016 a las 8:49

Como generar automaticamente un stacktrace cuando mi programa falla
jschmier

linux

Si bien ya se ha sugerido el uso de las funciones backtrace() en execinfo.h para imprimir un seguimiento de pila y salir correctamente cuando se obtiene una falla de segmentación, no veo ninguna mención de las complejidades necesarias para garantizar que el seguimiento posterior resultante apunte a la ubicación real de la falla (al menos para algunas arquitecturas – x86 y ARM).

Las dos primeras entradas en la cadena de marcos de pila cuando ingresa al controlador de señales contienen una dirección de retorno dentro del controlador de señales y una dentro de sigaction() en libc. El marco de pila de la última función llamada antes de la señal (que es la ubicación de la falla) se pierde.

Código

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 ucontext_t        *uc_link;
 stack_t           uc_stack;
 sigcontext_t      uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)\n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

Producción

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

Todos los peligros de llamar a las funciones backtrace() en un controlador de señal aún existen y no deben pasarse por alto, pero la funcionalidad que describí aquí me parece muy útil para depurar fallas.

Es importante tener en cuenta que el ejemplo que proporcioné está desarrollado/probado en Linux para x86. También he implementado con éxito esto en ARM usando uc_mcontext.arm_pc en lugar de uc_mcontext.eip.

Aquí hay un enlace al artículo donde aprendí los detalles de esta implementación:
http://www.linuxjournal.com/article/6391

  • En sistemas que usan GNU ld, recuerde compilar con -rdynamic para indicar al enlazador que agregue todos los símbolos, no solo los usados, a la tabla de símbolos dinámicos. Esto permite backtrace_symbols() para convertir direcciones a nombres de funciones

    – jschmier

    26 de marzo de 2010 a las 20:00

  • Además, debe agregar la opción “-mapcs-frame” a la línea de comando de GCC para generar marcos de pila en la plataforma ARM

    – qehgt

    1 de febrero de 2012 a las 15:53

  • Esto puede ser demasiado tarde, pero ¿podemos usar addr2line comando de alguna manera para obtener la línea exacta donde ocurrió el bloqueo?

    – friki entusiasta

    24/10/2012 a las 18:26

  • En versiones más recientes de glibc uc_mcontext no contiene un campo llamado eip. Ahora hay una matriz que necesita ser indexada, uc_mcontext.gregs[REG_EIP] es el equivalente.

    – mmlb

    14 de diciembre de 2012 a las 14:57


  • Para ARM, mis backtraces siempre tenían profundidad 1 hasta que agregué la opción -funwind-tables al compilador.

    – jfritz42

    10 de abril de 2013 a las 16:10

Como generar automaticamente un stacktrace cuando mi programa falla
jschmier

Aunque se ha proporcionado una respuesta correcta que describe cómo usar GNU libc backtrace() función1 y proporcioné mi propia respuesta que describe cómo garantizar que un seguimiento desde un controlador de señal apunte a la ubicación real de la falla2no veo ninguna mención de desmantelamiento Salida de símbolos C++ del seguimiento inverso.

Al obtener trazas inversas de un programa C++, la salida se puede ejecutar a través de c++filt1 desenredar los símbolos o utilizando abi::__cxa_demangle1 directamente.

  • 1 Linux y OS X
    Tenga en cuenta que c++filt y __cxa_demangle son específicos de GCC
  • 2 linux

El siguiente ejemplo de C++ Linux usa el mismo controlador de señal que mi otra respuesta y demuestra cómo c++filt se puede utilizar para desmantelar los símbolos.

Código:

class foo
{
public:
    foo() { foo1(); }

private:
    void foo1() { foo2(); }
    void foo2() { foo3(); }
    void foo3() { foo4(); }
    void foo4() { crash(); }
    void crash() { char * p = NULL; *p = 0; }
};

int main(int argc, char ** argv)
{
    // Setup signal handler for SIGSEGV
    ...

    foo * f = new foo();
    return 0;
}

Producción (./test):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

Salida despeinada (./test 2>&1 | c++filt):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

Lo siguiente se basa en el controlador de señales de mi respuesta original y puede reemplazar el controlador de señales en el ejemplo anterior para demostrar cómo abi::__cxa_demangle se puede utilizar para desmantelar los símbolos. Este controlador de señal produce la misma salida desmantelada que el ejemplo anterior.

Código:

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific

    std::cerr << "signal " << sig_num 
              << " (" << strsignal(sig_num) << "), address is " 
              << info->si_addr << " from " << caller_address 
              << std::endl << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    array[1] = caller_address;

    char ** messages = backtrace_symbols(array, size);    

    // skip first stack frame (points here)
    for (int i = 1; i < size && messages != NULL; ++i)
    {
        char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;

        // find parantheses and +address offset surrounding mangled name
        for (char *p = messages[i]; *p; ++p)
        {
            if (*p == '(') 
            {
                mangled_name = p; 
            }
            else if (*p == '+') 
            {
                offset_begin = p;
            }
            else if (*p == ')')
            {
                offset_end = p;
                break;
            }
        }

        // if the line could be processed, attempt to demangle the symbol
        if (mangled_name && offset_begin && offset_end && 
            mangled_name < offset_begin)
        {
            *mangled_name++ = '\0';
            *offset_begin++ = '\0';
            *offset_end++ = '\0';

            int status;
            char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);

            // if demangling is successful, output the demangled function name
            if (status == 0)
            {    
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << real_name << "+" << offset_begin << offset_end 
                          << std::endl;

            }
            // otherwise, output the mangled function name
            else
            {
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << mangled_name << "+" << offset_begin << offset_end 
                          << std::endl;
            }
            free(real_name);
        }
        // otherwise, print the whole line
        else
        {
            std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
        }
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}

1647571997 20 Como generar automaticamente un stacktrace cuando mi programa falla
Simón Steele

Podría valer la pena mirar Bloqueo de Googleun generador de volcados de memoria multiplataforma y herramientas para procesar los volcados.

  • Informa sobre cosas como fallas de segmentación, pero no informa ninguna información sobre excepciones de C++ no controladas.

    – DBedrenko

    2 de agosto de 2016 a las 11:15

1647571997 795 Como generar automaticamente un stacktrace cuando mi programa falla
usuario

No especificó su sistema operativo, por lo que esto es difícil de responder. Si está utilizando un sistema basado en gnu libc, es posible que pueda usar la función libc backtrace().

GCC también tiene dos componentes integrados que pueden ayudarlo, pero que pueden o no implementarse completamente en su arquitectura, y esos son __builtin_frame_address y __builtin_return_address. Ambos quieren un nivel entero inmediato (por inmediato, quiero decir que no puede ser una variable). Si __builtin_frame_address para un nivel dado no es cero, debería ser seguro obtener la dirección de retorno del mismo nivel.

  • Informa sobre cosas como fallas de segmentación, pero no informa ninguna información sobre excepciones de C++ no controladas.

    – DBedrenko

    2 de agosto de 2016 a las 11:15

1647571998 505 Como generar automaticamente un stacktrace cuando mi programa falla
Comunidad

Gracias a entusiastageek por llamar mi atención sobre la utilidad addr2line.

Escribí un script rápido y sucio para procesar el resultado de la respuesta proporcionada aquí: (¡muchas gracias a jschmier!) Usando la utilidad addr2line.

El script acepta un solo argumento: el nombre del archivo que contiene la salida de la utilidad de jschmier.

La salida debe imprimir algo como lo siguiente para cada nivel de la traza:

BACKTRACE:  testExe 0x8A5db6b
FILE:       pathToFile/testExe.C:110
FUNCTION:   testFunction(int) 
   107  
   108           
   109           int* i = 0x0;
  *110           *i = 5;
   111      
   112        }
   113        return i;

Código:

#!/bin/bash

LOGFILE=$1

NUM_SRC_CONTEXT_LINES=3

old_IFS=$IFS  # save the field separator           
IFS=$'\n'     # new field separator, the end of line           

for bt in `cat $LOGFILE | grep '\[bt\]'`; do
   IFS=$old_IFS     # restore default field separator 
   printf '\n'
   EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`  
   ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
   echo "BACKTRACE:  $EXEC $ADDR"
   A2L=`addr2line -a $ADDR -e $EXEC -pfC`
   #echo "A2L:        $A2L"

   FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
   FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
   echo "FILE:       $FILE_AND_LINE"
   echo "FUNCTION:   $FUNCTION"

   # print offending source code
   SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
   LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
   if ([ -f $SRCFILE ]); then
      cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
   else
      echo "File not found: $SRCFILE"
   fi
   IFS=$'\n'     # new field separator, the end of line           
done

IFS=$old_IFS     # restore default field separator 

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad