Как Собрать Си Программу в OS Windows

aee30d1ff8a0180bd0d45a28b40feeb3.png

Иногда возникает ситуация, когда надо что-то посчитать согласно сложному алгоритму прямо на LapTop/NetTop/DeskTop PC. При этом этот алгоритм написан на Си. Это может быть цифровой фильтр, дискретное преобразование Фурье, генератор QR кода, кусок линейной алгебры с векторами, какое-то тригонометрическое вычисление, программный модулятор, статистическая обработка случайной величины. Да всё, что угодно! То есть Вы хотите использовать язык Си как гибкий и быстрый калькулятор в Windows. Тут надо написать программу на Си.

Компьютер — это универсальный вычислитель

Или, например, Вы программируете микроконтроллеры на Си и хотите сделать симулятор прошивки в виде консольного приложения. Надо вам, например, прогнать модульные тесты платформа-независимого кода на «большом компьютере». Потом Вам 80% вероятность, что понадобится конфигуратор прошивки по UART. Потом Вам понадобится консольное приложение Loader для загрузки по UART самой прошивки через BootLoader.

Потом понадобится крохотная PC-утилита синхронизации для часов реального времени с PC.

Почему именно Си?

1--Эту LapTop утилиту стоит писать на том же языке, что и прошивку хотя бы по той причине, что можно пере использовать кодовую базу из микроконтроллеров для программирования на DeskTop (е).


2--Дело в том что язык программирования Си — это самый простой язык программирования из тех, что всё еще более или менее используется в промышленной разработке. По факту в Си только функции и переменные. Тут нет никаких виртуальных функций, шаблонов, делегатов и прочих концепций. В Си всё предельно просто и конкретно.

---

Разработка под PC это не сross компиляция, как в случае со сборкой артефактов для микроконтроллера, и тут в PC всё в какой-то степени проще. При сборке Си приложения не надо думать о файле конфигурации компоновщика, как мы это привыкли делать для cross компиляции артефактов для микроконтроллеров (файлы *.ld).

Сперва определимся для какого Target (а) надо собрать бинарь. Надо узнать какой у нас на материнской плате установлен микропроцессор. Такую информацию может показать утилита CPUZ.

0cd73e6c09eaf3a9b6edab7cd4c80e22.png

В данном случае у меня на одном компьютере Intel Celeron J4125 2Ghz, 4x cores, L1 32kByte, L2 4MByte, 10W. На другом компьютере установлен 64 разрядный микропроцессор AMD Ryzen 5 PRO 3400GE 3.30 GHz.

ced0bd8b59701b30c0594e770c0cbf0e.png

Но это даже не так важно. Важно какой у нас Instruction Set. В данном случае — x86–64. Это значит, что у нас 64-битный процессор. Получается, что у нас есть выбор: либо ставить 64-битный компилятор Си, либо накатывать 32-битный компилятор Си.

Как правило, нам приходится работать на разных процессорах, однако мы в OS Windows этого даже не замечаем.

Units:

text

bit

N

N

kByte

kByte

MByte

Процессор

bitness

cores

Threads

L1

L2

L3

1

AMD Ryzen 5 PRO 3400GE

64

4

8

32

512

4

2

Intel Celeron J4125

4

4

32

3

Intel Core i7 8550U

64

4

8

32

256

8

Одновременно с этим наш имитатор прошивки должен собираться и запускаться на всех окружениях настольных компьютеров: на работе, дома, в гараже.

Как и в любом деле сначала надо определится с терминологией.

Терминология

Компилятор — программа, переводящая написанный на языке программирования читаемый понятный человеком текст, в набор машинных кодов (человеко-нечитаемый бинарный код). Программисты это люди, который как никто приближены к абстракциям. Любой язык программирования, в частности Си — это уровень на коротком можно не думать о наборе команд данного микропроцессора. У процессора нет никаких переменных и функций. Переменные существуют только в сознании программиста. Поэтому нам нужен компилятор Си. Компилятор для каждого Cи файлика производит *.o файлик с родным для данного процессора машинным кодом.

Компоновщик (Linker)— утилита, которая склеивает бинарные файлы *.o в один монолитный исполнительный бинарный файл программы. В нашем случае это *.exe.

Артефакт — результат работы ToolChain (а). В нашем случае это *.exe файл с бинарным файлом программы.

Что надо из софтвера?

Текстовый редактор (Text Editor)

Прежде всего нужен какой-то текстовый редактор, чтобы написать этот самый исходный текст программы на Си. Тут вариантов масса. NotePad++, Eclipse, MS VS Code.

Сборщик (Build Tools)

В компьютерах ничего само собой не происходит. Компьютеры — это самые ленивые и безынициативные сущности. Им всё надо объяснять максимально подробно и понятно. Поэтому надо явно указать из каких *.с файлов мы хотим собирать программу. Эти файлы надо как-то перечислить проиндексировать. Для этого была создана специальная утилита называемая make. Идея проста. Создается текстовый файл (Makefile) и в нем прописывается правильная последовательность вызова консольных утилит, которая приведет к тому, что на жестком диске появится исполнительный файл с программой.

Препроцессор (Preprocessor)

Это такая консольная утилита (cpp.exe), которая вставляет и заменяет куски текста. Нужна чисто ради удобства написание текста. Препроцессор позволяет полностью исключить такое нехорошее явление как дублирование программного кода. При этом препроцессору абсолютно всё равно с каким языком программирования работать (Cи, C++, DeviceTree, Graphviz, скрипты компоновки и т. п.). Для препроцессора любой язык программирования — это просто текст.

Теперь рассмотрим практические аспекты.

Какой выбрать компилятор Си кода?

Тут есть несколько бесплатных вариантов на выбор.

Компилятор

разрядность генерируемого кода

1

СygWin

64

2

MinGW

32

3

Mingw-w64

64

4

clang

64

Для программистов микроконтроллеров я настоятельно рекомендую выбрать именно MinGW. Дело в том, что MinGW генерирует 32-битный код. Это как раз соответствует тому, что большинство микроконтроллеров (например ARM Cortex Mx) как раз 32-битные. И Вы так достигните большей совместимости вашего консольного приложения между микроконтроллером и Windows процессом.

Вторая причина по которой надо использовать компилятор C:\MinGW\bin\gcc.exe заключается в том, что в окружении MinGW есть заголовочный фал conio.h, который определяет функцию kbhit(). Это нам понадобится для имитации на PC текстового терминала UART-CLI консоли в stdout/stdin.

>C:\MinGW\bin\gcc.exe  --version
gcc.exe (MinGW.org GCC-6.3.0-1) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Набор утилит MinGW преспокойно скачивается и устанавливается как любая другая программа под OS Windows.

Компоновщик (Linker)

Компоновкой занимается утилита ld, которая вызывает collect2. Именно утилита collect2.exe будет выдавать ошибки, если Вы будите вызывать функции без определения их тела.

Что вообще надо из софтвера?

Вот минимальный джентельменский набор для того чтобы собрать программу на Си.

Назначение утилиты

Название утилиты

1

Текстовый редактор

NotePad++.exe

2

Препроцессор

cpp.exe

3

Компилятор

gcc.exe

4

Консольная утилита для удаления файлов или папок

rm

5

Компоновщик

ld.exe

6

Утилита управления ToolChain (ом). Она решает что и в какой последовательности собирать, чтобы получить артефакты

make.exe

7

Утилита анализа получившегося бинаря. Аналог readelf.exe из мира программирования микроконтроллеров

PE explore.exe

Традиционно программы на Си собирают из make скриптов. Вот минималистический makefile для сборки много файлового Си-проекта на Windows

MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
INCDIR += -I$(MK_PATH)

WORKSPACE_LOC:= $(MK_PATH)../../
$(info  WORKSPACE_LOC= $(WORKSPACE_LOC))
INCDIR += -I$(WORKSPACE_LOC)

BUILDDIR := $(MK_PATH)/Build

SRC_PATH :=  $(dir $(abspath $(dir $$PWD) ))
#@echo $(error SRC_PATH=$(SRC_PATH))

OBJDIR := $(SRC_PATH)obj
# the compiler to use

OPT += -DHAS_GCC
CC = C:\MinGW\bin\gcc.exe

# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS += -g

#Generate code for 32-bit ABI
CFLAGS += -m32

CFLAGS += -std=c11 -fshort-enums
#CFLAGS += -Og
CFLAGS  += -O0
#CFLAGS  += -Wall
#CFLAGS  +=-pedantic
#CFLAGS += -ftime-report

#files to link:
LFLAGS += -static
#LFLAGS += -lm

EXECUTABLE=firmware_simulator_x86_m

include $(MK_PATH)config.mk

ifeq ($(CLI),Y)
    include $(MK_PATH)cli_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)test_config.mk
endif

ifeq ($(UNIT_TEST),Y)
    include $(MK_PATH)diag_config.mk
endif

include $(WORKSPACE_LOC)code_base.mk


#@echo $(error SOURCES_C= $(SOURCES_C))
INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR))
#@echo $(error INCDIR= $(INCDIR))
OBJ := $(patsubst %.c, %.o, $(SOURCES_C))
OBJ := $(subst /cygdrive/c/,C:/, $(OBJ))
#@echo $(error OBJ= $(OBJ))

.PHONY:all

all:$(OBJ) $(EXECUTABLE)

$(EXECUTABLE): $(OBJ)
	$(CC) $(CFLAGS)  $(OBJ) $(LFLAGS) -o $(EXECUTABLE).exe 

%.o: %.c
	$(CC) $(CFLAGS) $(INCDIR) $(OPT) -c $< -o $@ 

clean:
	rm -r $(EXECUTABLE) $(OBJ) 

Тут файлы config.mk, cli_config.mk, test_config.mk и diag_config.mk это просто файлы с перечислением набора переменных окружения для выборочной сборки конкретных исходников из общей кодовой базы. Вот корневой makefile для подключения разных программных компонентов code_base.mk

ifneq ($(CODE_BASE_MK),Y)
    CODE_BASE_MK=Y
    $(info CodeBase Config)

    #@echo $(error WORKSPACE_LOC=$(WORKSPACE_LOC))
    INCDIR += -I$(WORKSPACE_LOC)

    ifeq ($(THIRD_PARTY),Y)
        include $(WORKSPACE_LOC)/third_party/third_party.mk
    endif

    ifeq ($(APPLICATIONS),Y)
        include $(WORKSPACE_LOC)/applications/applications.mk
    endif

    ifeq ($(CONNECTIVITY),Y)
        include $(WORKSPACE_LOC)/connectivity/connectivity.mk
    endif

    ifeq ($(CONTROL),Y)
        include $(WORKSPACE_LOC)/control/control.mk
    endif
    
    ifeq ($(COMPUTING),Y)
        #@echo $(error COMPUTING=$(COMPUTING))
        include $(WORKSPACE_LOC)/computing/computing.mk
    endif

    ifeq ($(SENSITIVITY),Y)
        #@echo $(error SENSITIVITY=$(SENSITIVITY))
        include $(WORKSPACE_LOC)/sensitivity/sensitivity.mk
    endif

    ifeq ($(STORAGE),Y)
        #@echo $(error STORAGE=$(STORAGE))
        include $(WORKSPACE_LOC)/storage/storage.mk
    endif

    ifeq ($(UNIT_TEST),Y)  
        include $(WORKSPACE_LOC)/unit_tests/unit_test.mk
    endif
endif

Конечный make файл может выглядеть например так. Вот тут и индексируют конечные *.c файлы и определяют ключевые слова для препроцессора (начинаются на HAS_XXXXX).


$(info SCHMITT_TRIGGER_MK_INC=$(SCHMITT_TRIGGER_MK_INC))
ifneq ($(SCHMITT_TRIGGER_MK_INC),Y)
    SCHMITT_TRIGGER_MK_INC=Y

    mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
    $(info Build  $(mkfile_path) )

    SCHMITT_TRIGGER_DIR = $(COMPUTING_DIR)/schmitt_trigger

    INCDIR += -I$(SCHMITT_TRIGGER_DIR)
    SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger.c

    SCHMITT_TRIGGER=Y
    OPT += -DHAS_SCHMITT_TRIGGER

    ifeq ($(DIAG),Y)
        OPT += -DHAS_SCHMITT_TRIGGER_DIAG
        SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_diag.c
    endif

    ifeq ($(CLI),Y)
        ifeq ($(SCHMITT_TRIGGER_COMMANDS),Y)
            OPT += -DHAS_SCHMITT_TRIGGER_COMMANDS
            SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_commands.c
        endif
    endif
endif

При первой сборке проекта скорее всего выскочит вот эта ошибка. Это значит, что надо переустановить MinGW.


C:\Users\username\AppData\Local\Temp\ccT9XWou.s:54: Error: invalid instruction suffix for `push'
C:\Users\username\AppData\Local\Temp\ccfvWBon.s:19: Error: invalid instruction suffix for `pop'

Надо чтобы команда gcc.exe -dumpmachine после установки показывала mingw32

>C:\MinGW\bin\gcc.exe -dumpmachine
mingw32


Содержимое того Makefile можно представить в виде вот этой простенькой блок схемы ToolChain (а). Тут можно визуально проследить какой путь проходит *.с файл с момента написания до исполнения в Windows.

bb847edc7b408a5a4d9d46d54cf85b23.jpg

В остальном сборка на PC не отличается от сборки для микроконтроллера. В этом и достоинство сборки из Make. При работе с make cборка под любые процессоры выглядит плюс/минус одинаково. Просто по другому определенные переменные окружения: CC LD и т. п. Все те же *.mk файлы подтянутся что и в кодовой базе для прошивок.

Про то как происходит сборка прошивок из-под скриптов можно почитать в этом тексте:
Настройка ToolChain (а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB https://habr.com/ru/articles/673522/

Когда собралась вся кодовая база бинарь получился всего 1,9MByte

2ba7aff0806952dbac9640492f035da0.png

Отладка имитатора прошивки

Артефактом сборки является файл *.exe. Его можно запустить вызвав его из командной строки cmd.

e2dfa1d115fc9da0eb45a0510ce827f2.png

Тут происходит имитация UART-CLI терминала только вместо UART выступают файлы sdtin (аналог UART-RX)/stdout (аналог UART-TX).

Вывод

Сборка кода на Cи для DeskTop это весьма полезный навык при отладке микроконтроллерных прошивок. Можно отладить огромное количество платформа независимого кода: CRC, обработку строчек, триггер Шмитта, бинарные протоколы, и т.п.

Также можно собрать проект разными компиляторами: GCC, Clang. И тем самым найти и устранить больше ошибок в кодовой базе.

Как видите, в сборке Си программ на PC, ровным счетом, нет ничего сложного. Надеюсь это поможет большему количеству программистов микроконтроллеров отлаживать свои приложения на DeskTop PC и создавать отличные программные продукты.

Links

https://metanit.com/c/tutorial/1.2.php

https://www.youtube.com/watch? v=SRMUJHY-0gA

https://www.youtube.com/watch? v=-lLctanPefY

https://sourceforge.net/projects/mingw/

Генерация зависимостей внутри программы
https://habr.com/ru/articles/765424/

Сборка firmware для CC2652 из Makefile
https://habr.com/ru/articles/726352/

Почему Важно Собирать Код из Скриптов
https://habr.com/ru/articles/723054/

Настройка ToolChain (а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB
https://habr.com/ru/articles/673522/

Вопросы

1--Какой путь проходит с файл с момента написания до момента исполнения?

2--Что происходит между нажатием на Enter при запуске консольной утилиты в cmd и запуском функции main ()?

© Habrahabr.ru