Evitar el principal (punto de entrada) en un programa C

10 minutos de lectura

avatar de usuario
Karthik Balagurú

¿Es posible evitar el punto de entrada (principal) en un programa C? En el siguiente código, ¿es posible invocar el func() llamar sin llamar vía main() en el siguiente programa? En caso afirmativo, ¿cómo hacerlo y cuándo sería necesario y por qué se da tal disposición?

int func(void)
{
     printf("This is func \n");
     return 0;
}

int main(void)
{
     printf("This is main \n");
     return 0;
}

  • ¿Por qué necesitarías hacer eso?

    – Oded

    31 de julio de 2010 a las 17:49

  • En C++, el ctor de un objeto estático global puede ejecutarse antes que main().

    – arena

    31 de julio de 2010 a las 17:57

  • Para reformular la pregunta de Oded: Díganos lo que quiere lograr y le diremos cómo lograrlo, probablemente sin eludir main. (Más específicamente: algunos SOer lo son. Mi falta de conocimiento de C me impide ayudarlo).

    – MvanGeest

    31 de julio de 2010 a las 18:00


  • Es una pregunta que encontré mientras discutía varias preguntas complicadas de C 🙂 Yo también me pregunto la necesidad y el uso de la misma.

    – Karthik Balagurú

    31 de julio de 2010 a las 18:01


  • En C – No. Sin embargo, algunos compiladores/plataformas pueden proporcionar medios para lograrlo. ¿Alguna plataforma en particular que tengas en mente?

    – nos

    31 de julio de 2010 a las 22:24

Si está usando gcc, encontré un hilo que decía que puede usar el -e parámetro de línea de comandos para especificar un punto de entrada diferente; para que puedas usar func como su punto de entrada, lo que dejaría main no usado.

Tenga en cuenta que esto en realidad no le permite llamar a otra rutina en lugar de main. En su lugar, le permite llamar a otra rutina en lugar de _startque es la rutina de inicio de libc: hace algunas configuraciones y luego eso llamadas main. Entonces, si hace esto, perderá parte del código de inicialización que está integrado en su biblioteca de tiempo de ejecución, que podría incluir cosas como analizar argumentos de la línea de comandos. Lea sobre este parámetro antes de usarlo.

Si está utilizando otro compilador, puede haber o no un parámetro para esto.

  • información interesante + 1 por eso. ¿Proporciona también disposiciones para determinar la presencia de diferentes puntos de entrada?

    – Karthik Balagurú

    31 de julio de 2010 a las 21:13

  • Probablemente no, ya que ese parámetro afecta al enlazador, no al compilador. Pero, ¿por qué necesitarías detectarlo? Usted es quien está compilando su aplicación, por lo que sabrá si la está creando de esta manera o no.

    – Joe White

    2 de agosto de 2010 a las 1:22

  • También puede usar el comando ENTRY en el enlazador. Ver ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_24.html

    – mashrur

    6 de junio de 2019 a las 14:46

avatar de usuario
rberteig

Al crear firmware de sistemas integrados para que se ejecute directamente desde la ROM, a menudo evito nombrar el punto de entrada main() para enfatizar a un revisor de código la naturaleza especial del código. En estos casos, estoy proporcionando una versión personalizada del módulo de inicio de tiempo de ejecución de C, por lo que es fácil reemplazar su llamada a main() con otro nombre como BootLoader().

Yo (o mi proveedor) casi siempre tengo que personalizar el inicio del tiempo de ejecución de C en estos sistemas porque no es inusual que la RAM requiera un código de inicialización para que comience a funcionar correctamente. Por ejemplo, los chips DRAM típicos requieren una cantidad sorprendente de configuración de su hardware de control y, a menudo, requieren un retraso sustancial (miles de ciclos de reloj de bus) antes de que sean útiles. Hasta que se complete, es posible que ni siquiera haya un lugar para colocar la pila de llamadas, por lo que es posible que el código de inicio no pueda llamar a ninguna función. Incluso si los dispositivos RAM están operativos al momento del encendido, casi siempre hay una cantidad de hardware de selección de chip o una FPGA o dos que requieren inicialización antes de que sea seguro dejar que el tiempo de ejecución de C comience su inicialización.

Cuando un programa escrito en C se carga y se inicia, algún componente se encarga de hacer que el entorno en el que main() se llama existir. En Unix, Linux, Windows y otros entornos interactivos, gran parte de ese esfuerzo es una consecuencia natural del componente del sistema operativo que carga el programa. Sin embargo, incluso en estos entornos hay una cierta cantidad de trabajo de inicialización que hacer antes main() puede ser llamado. Si el código es realmente C++, entonces puede haber una cantidad sustancial de trabajo que incluye llamar a los constructores para todas las instancias de objetos globales.

Los detalles de todo esto son manejados por el enlazador y sus archivos de configuración y control. El enlazador ld(1) tiene un archivo de control muy elaborado que le dice exactamente qué segmentos incluir en la salida, en qué direcciones y en qué orden. Encontrar el archivo de control del enlazador que está usando implícitamente para su cadena de herramientas y leerlo puede ser instructivo, al igual que el manual de referencia para el enlazador mismo y el estándar ABI que deben seguir sus ejecutables para ejecutarse.

Editar: Para responder más directamente a la pregunta formulada en un contexto más común: “¿Puedes llamar a foo en lugar de main?” La respuesta es “Tal vez, pero solo siendo engañoso”.

En Windows, un ejecutable y una DLL tienen casi el mismo formato de archivo. Es posible escribir un programa que cargue una DLL arbitraria nombrada en tiempo de ejecución, localice una función arbitraria dentro de ella y la llame. Uno de esos programas en realidad se envía como parte de una distribución estándar de Windows: rundll32.exe.

Dado que un archivo .EXE puede ser cargado e inspeccionado por las mismas API que manejan archivos .DLL, en principio si el .EXE tiene una sección EXPORTAR que nombra la función foo, entonces se podría escribir una utilidad similar para cargarla e invocarla. No necesitas hacer nada especial con main, por supuesto, ya que ese será el punto de entrada natural. Por supuesto, el tiempo de ejecución de C que se inicializó en su utilidad podría no ser el mismo tiempo de ejecución de C que se vinculó con su ejecutable. (Busque en Google “DLL Hell” para obtener una pista). En ese caso, es posible que su utilidad deba ser más inteligente. Por ejemplo, podría actuar como un depurador, cargar el EXE con un punto de interrupción en maincorra hasta ese punto de ruptura, luego cambie la PC para que apunte a o hacia foo y continuar desde allí.

Algún tipo de truco similar podría ser posible en Linux, ya que los archivos .so también son similares en algunos aspectos a los verdaderos ejecutables. Ciertamente, el enfoque de actuar como un depurador podría funcionar.

  • Quizás más de lo que el OP siempre quiso saber, ¡pero una lectura fascinante en cualquier caso!

    -Carl Smotricz

    31 de julio de 2010 a las 22:09

  • @Carl, Incluso si nunca necesita usar el conocimiento, puede ser útil echar un vistazo debajo del capó de vez en cuando… e intentar implementar un enlazador es un ejercicio extremadamente instructivo 😉

    – R. Berteig

    31 de julio de 2010 a las 22:19

  • ¡Absolutamente! De hecho, hice algo como esto para un sistema integrado hace unos 20 años, reubicando de forma personalizada el código compilado de Turbo C para un sistema integrado. Aún así, me alegro de no tener que hacer eso nunca más 🙂

    -Carl Smotricz

    31 de julio de 2010 a las 22:22

  • Construir un cargador para poner un EXE en una ROM también es un ejercicio entretenido. Odio ponerme como un “viejo loco” en este punto, pero existe la tentación de quejarme sobre cómo estos jóvenes nunca tienen que ver realmente los bits que están usando. Luego pasamos a las historias sobre la carga manual del cargador de arranque desde los interruptores del panel frontal para que el lector de latas de cinta de papel pueda cargar el programa real… 😉

    – R. Berteig

    31 de julio de 2010 a las 22:30

Una regla general sería que el cargador suministrado por el sistema siempre ejecutar principal. Con suficiente autoridad y competencia, teóricamente podría escribir un cargador diferente que hiciera otra cosa.

Cambie el nombre de main para que sea func y func para que sea main y llame a func desde nombre.

Si tiene acceso a la fuente, puede hacerlo y es fácil.

Si está utilizando un compilador de código abierto como GCC o un compilador destinado a sistemas integrados, puede modificar el inicio del tiempo de ejecución de C (CRT) para que comience en cualquier punto de entrada que necesite. En GCC este código está en crt0.s. Por lo general, este código está parcial o totalmente en ensamblador; para la mayoría de los compiladores de sistemas integrados, se proporcionará un ejemplo o un código de inicio predeterminado.

Sin embargo, un enfoque más simple es simplemente ‘ocultar’ main() en una biblioteca estática que vincula a su código. Si esa implementación de main() se ve así:

int main(void)
{
    func() ;
}

Luego, a todos los efectos, se verá como si el punto de entrada del usuario fuera func(). Así es como funcionan muchos marcos de aplicación con puntos de entrada distintos de main(). Tenga en cuenta que debido a que se encuentra en una biblioteca estática, cualquier definición de usuario de main() anulará esa versión de biblioteca estática.

avatar de usuario
Oleg

La solución depende del compilador y el enlazador que utilice. siempre es eso no main es el punto de entrada real de la aplicación. El punto de entrada real hace algunas inicializaciones y llamadas, por ejemplo main. Si escribe programas para Windows usando Visual Studio, puede usar el modificador /ENTRY del enlazador para sobrescribir el punto de entrada predeterminado mainCRTStartup y llama func() en lugar de main():

#ifdef NDEBUG
void mainCRTStartup()
{
    ExitProcess (func());
}
#endif

Si es una práctica estándar si escribe la aplicación más pequeña. En ese caso, recibirá restricciones en el uso de las funciones de C-Runtime. Debe usar la función API de Windows en lugar de la función C-Runtime. Por ejemplo en lugar de printf("This is func \n") Deberías usar OutputString(TEXT("This is func \n")) donde OutputString se aplican sólo con respecto a WriteFile o WriteConsole:

static HANDLE g_hStdOutput = INVALID_HANDLE_VALUE;
static BOOL g_bConsoleOutput = TRUE;

BOOL InitializeStdOutput()
{
    g_hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
    if (g_hStdOutput == INVALID_HANDLE_VALUE)
        return FALSE;

    g_bConsoleOutput = (GetFileType (g_hStdOutput) & ~FILE_TYPE_REMOTE) != FILE_TYPE_DISK;
#ifdef UNICODE
    if (!g_bConsoleOutput && GetFileSize (g_hStdOutput, NULL) == 0) {
        DWORD n;

        WriteFile (g_hStdOutput, "\xFF\xFE", 2, &n, NULL);
    }
#endif

    return TRUE;
}

void Output (LPCTSTR pszString, UINT uStringLength)
{
    DWORD n;

    if (g_bConsoleOutput) {
#ifdef UNICODE
        WriteConsole (g_hStdOutput, pszString, uStringLength, &n, NULL);
#else
        CHAR szOemString[MAX_PATH];
        CharToOem (pszString, szOemString);
        WriteConsole (g_hStdOutput, szOemString, uStringLength, &n, NULL);
#endif
    }
    else
#ifdef UNICODE
        WriteFile (g_hStdOutput, pszString, uStringLength * sizeof (TCHAR), &n, NULL);
#else
    {
        //PSTR pszOemString = _alloca ((uStringLength + sizeof(DWORD)));
        CHAR szOemString[MAX_PATH];
        CharToOem (pszString, szOemString);
        WriteFile (g_hStdOutput, szOemString, uStringLength, &n, NULL);
    }
#endif
}

void OutputString (LPCTSTR pszString)
{
    Output (pszString, lstrlen (pszString));
}

avatar de usuario
Gian

Esto realmente depende de cómo esté invocando el binario, y va a ser razonablemente específico de la plataforma y el entorno. La respuesta más obvia es simplemente cambiar el nombre del símbolo “principal” a otra cosa y llamar a “función” “principal”, pero sospecho que eso no es lo que está tratando de hacer.

¿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