Избегание основного (точки входа) в программе на Си - PullRequest
17 голосов
/ 31 июля 2010

Можно ли избежать точки входа (основной) в C-программе.В приведенном ниже коде можно ли вызвать вызов func() без вызова через main() в приведенной ниже программе?Если да, как это сделать и когда это потребуется, и почему предоставляется такое положение?

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

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

Ответы [ 7 ]

23 голосов
/ 31 июля 2010

Если вы используете gcc, я обнаружил поток, в котором сказано, что вы можете использовать параметр командной строки -e , чтобы указать другую точку входа;так что вы могли бы использовать func в качестве точки входа, что оставило бы main неиспользованным.

Обратите внимание, что это фактически не позволяет вам вызывать другую подпрограмму вместо main.Вместо этого он позволяет вам вызывать другую подпрограмму вместо _start, которая является подпрограммой запуска libc - она ​​выполняет некоторую настройку, а затем она вызывает main.Поэтому, если вы сделаете это, вы потеряете часть кода инициализации, встроенного в вашу библиотеку времени выполнения, который может включать такие вещи, как анализ аргументов командной строки.Прочтите этот параметр перед его использованием.

Если вы используете другой компилятор, для этого может быть или не быть параметр.

16 голосов
/ 01 августа 2010

При создании встроенного программного обеспечения встроенного программного обеспечения для запуска непосредственно из ПЗУ, я часто избегаю называть точку входа main(), чтобы подчеркнуть рецензенту кода особый характер кода.В этих случаях я предоставляю настроенную версию модуля запуска среды выполнения C, так что его вызов на main() легко заменить другим именем, например BootLoader().

I (или моим поставщиком).почти всегда приходится настраивать запуск среды выполнения C в этих системах, поскольку для ОЗУ нередко требуется код инициализации для правильной работы.Например, типичные микросхемы DRAM требуют удивительного количества конфигурации своего управляющего оборудования и часто требуют значительной (тысячи тактов шины) задержки, прежде чем они будут полезны.Пока это не будет завершено, может даже не быть места для размещения стека вызовов, поэтому код запуска может не вызывать какие-либо функции.Даже если устройства ОЗУ работают при включении, почти всегда имеется некоторое количество аппаратного обеспечения для выбора микросхемы или ПЛИС или двух, которым требуется инициализация, прежде чем можно будет безопасно запустить среду инициализации C.

Когдапрограмма, написанная на языке C, загружается и запускается, некоторый компонент отвечает за создание среды, в которой вызывается main().В Unix, Linux, Windows и других интерактивных средах большая часть этих усилий является естественным следствием компонента ОС, который загружает программу.Однако даже в этих средах необходимо выполнить определенную работу по инициализации, прежде чем можно будет вызвать main().Если код действительно C ++, тогда может потребоваться значительный объем работы, который включает в себя вызов конструкторов для всех экземпляров глобальных объектов.

Подробности всего этого обрабатываются компоновщиком и его файлами конфигурации и управления,Компоновщик ld (1) имеет очень сложный управляющий файл, в котором точно указано, какие сегменты включить в вывод, по каким адресам и в каком порядке.Поиск управляющего файла компоновщика, который вы неявно используете для своей цепочки инструментов, и чтение его могут быть поучительными, как и справочное руководство для самого компоновщика и стандарта ABI, которым должны следовать ваши исполняемые файлы.

Редактировать: Чтобы более прямо ответить на вопрос, заданный в более общем контексте: «Можете ли вы называть foo вместо main?»Ответ: «Может быть, но только из-за хитрости».

В Windows исполняемый файл и DLL имеют практически одинаковый формат файла.Можно написать программу, которая загружает произвольную DLL-библиотеку с именем во время выполнения, находит в ней произвольную функцию и вызывает ее.Одна такая программа на самом деле поставляется как часть стандартного дистрибутива Windows: rundll32.exe.

Поскольку файл .EXE можно загружать и проверять с помощью тех же API, которые обрабатывают файлы .DLL, вВ принципе, если в .EXE есть раздел EXPORTS с именем функции foo, то для ее загрузки и вызова можно написать аналогичную утилиту.Конечно, вам не нужно делать ничего особенного с main, поскольку это будет естественной точкой входа.Конечно, среда выполнения C, которая была инициализирована в вашей утилите, может отличаться от среды выполнения C, которая была связана с вашим исполняемым файлом.(Google для «DLL Hell» для подсказки.) В этом случае ваша утилита может быть умнее.Например, он может выступать в качестве отладчика, загружать EXE-файл с точкой останова на main, запускать до этой точки останова, а затем изменять ПК так, чтобы он указывал на или на foo, и продолжать оттуда.

Некоторый подобный обман может быть возможен в Linux, так как .so файлы также похожи в некоторых отношениях на настоящие исполняемые файлы.Конечно, подход, действующий как отладчик, можно заставить работать.

5 голосов
/ 31 июля 2010

Практическое правило заключается в том, что загрузчик, предоставляемый системой, всегда запускает main. С достаточными полномочиями и компетенцией вы можете теоретически написать другой загрузчик, который бы делал что-то еще.

4 голосов
/ 24 января 2011

Переименуйте main в func, а func в main и вызовите func от имени.

Если у вас есть доступ к источнику, вы можете сделать это, и это легко.

3 голосов
/ 31 июля 2010

Если вы используете компилятор с открытым исходным кодом, такой как GCC, или компилятор, предназначенный для встроенных систем, вы можете изменить запуск среды выполнения C (CRT) для запуска в любой точке входа, которая вам нужна. В GCC этот код находится в crt0.s. Обычно этот код частично или полностью находится на ассемблере, для большинства компиляторов встроенных систем будет предоставлен пример или код запуска по умолчанию.

Однако более простой подход - просто «спрятать» main () в статической библиотеке, на которую вы ссылаетесь в своем коде. Если эта реализация main () выглядит следующим образом:

int main(void)
{
    func() ;
}

Тогда все намерения и цели будут выглядеть так, как если бы точка входа пользователя была func (). Это то, как много прикладных сред с точками входа, отличными от main (), работают. Обратите внимание, что поскольку он находится в статической библиотеке, любое пользовательское определение main () будет переопределять эту версию статической библиотеки.

1 голос
/ 01 августа 2010

Решение зависит от используемого компилятора и компоновщика. Всегда является то, что не main является реальной точкой входа в приложение. Реальная точка входа выполняет некоторые инициализации и вызывает, например, main. Если вы пишете программы для Windows с использованием Visual Studio, вы можете использовать переключатель / ENTRY компоновщика, чтобы перезаписать точку входа по умолчанию mainCRTStartup и вызвать func() вместо main():

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

Если это стандартная практика, если вы пишете самое маленькое приложение. В этом случае вы получите ограничения в использовании функций C-Runtime. Вы должны использовать функцию Windows API вместо функции C-Runtime. Например, вместо printf("This is func \n") следует использовать OutputString(TEXT("This is func \n")), где OutputString реализованы только в отношении WriteFile или 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));
}
0 голосов
/ 31 июля 2010

Это действительно зависит от того, как вы вызываете двоичный файл, и будет зависеть от платформы и среды. Самый очевидный ответ - просто переименовать «главный» символ в другое и назвать «func» «основным», но я подозреваю, что это не то, что вы пытаетесь сделать.

...