Предотвращение основного (точка входа) в программе C

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

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

int main(void)
{
     printf("This is main \n");
     return 0;
}
17
задан Karthik Balaguru 31 July 2010 в 18:21
поделиться

6 ответов

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

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

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

23
ответ дан 30 November 2019 в 10:36
поделиться

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

Мне (или моему поставщику) почти всегда приходится настраивать запуск среды выполнения C в этих системах, потому что для оперативной памяти нет ничего необычного в том, что для ее правильной работы требуется код инициализации. Например, типичные микросхемы DRAM требуют удивительного количества конфигурации своего управляющего оборудования и часто требуют значительной задержки (тысячи тактов шины), прежде чем они станут полезными. Пока это не будет выполнено, может не хватить места для размещения стека вызовов, поэтому код запуска не сможет вызывать какие-либо функции. Даже если устройства RAM работают при включении, почти всегда есть некоторое количество оборудования для выбора микросхемы или одна или две FPGA, которые требуют инициализации, прежде чем можно будет безопасно позволить среде выполнения 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 также в некоторых отношениях похожи на настоящие исполняемые файлы. Конечно, можно заставить работать как отладчик.

16
ответ дан 30 November 2019 в 10:36
поделиться

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

0
ответ дан 30 November 2019 в 10:36
поделиться

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

5
ответ дан 30 November 2019 в 10:36
поделиться

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

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

int main(void)
{
    func() ;
}

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

3
ответ дан 30 November 2019 в 10:36
поделиться

Решение зависит от компилятора и компоновщика, которые вы используете. Всегда not 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));
}
1
ответ дан 30 November 2019 в 10:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: