programing

C와 어셈블러는 실제로 무엇을 컴파일합니까?

prostudy 2022. 7. 24. 21:24
반응형

C와 어셈블러는 실제로 무엇을 컴파일합니까?

그래서 C(+) 프로그램은 실제로는 단순한 바이너리로 컴파일되지 않는다는 것을 알게 되었습니다(죄송합니다만, 여기서 틀렸을지도 모릅니다).다양한 것(심볼 테이블, OS 관련 등)으로 컴파일 됩니다만,

  • 어셈블러는 순수 바이너리로 컴파일합니까?즉, 사전 정의된 문자열 등의 리소스 외에 추가 항목이 없습니다.

  • C가 일반 바이너리가 아닌 다른 것으로 컴파일 할 경우, 어떻게 그 작은 어셈블러 부트로더가 HDD에서 메모리로 명령어를 복사하여 실행할 수 있을까요?제 말은, 아마도 C로 쓰여져 있는 OS 커널이 일반 바이너리와는 다른 것으로 컴파일 되어 있는 경우, 부트로더는 어떻게 처리합니까?

편집: 어셈블러는 기계 명령어 세트만 있기 때문에 컴파일이 되지 않는다는 것을 알고 있습니다.어셈블러가 무엇을 "어셈블리"하는지 좋은 단어를 찾을 수 없었습니다.댓글로 남겨주시면 바꿔드릴게요.

C는 보통 어셈블러에 컴파일합니다.그것은 가난한 컴파일러 라이터의 생활을 용이하게 하기 때문입니다.

어셈블리 코드는 항상 재배치 가능한 개체 코드에 조립됩니다("컴파일"이 아닙니다).이것은 바이너리 머신 코드와 바이너리 데이터라고 생각할 수 있지만, 많은 장식과 메타데이터가 포함되어 있습니다.주요 부분은 다음과 같습니다.

  • 코드와 데이터는 명명된 "섹션"에 나타납니다.

  • 재배치 가능한 오브젝트 파일에는 라벨 정의가 포함될 수 있습니다.이러한 정의는 섹션 내의 위치를 참조합니다.

  • 재배치 가능한 객체 파일에는 다른 곳에서 정의된 라벨 값으로 채워지는 "구멍"이 포함될 수 있습니다.이러한 구멍의 공식 명칭은 이전 엔트리입니다.

예를 들어, 이 프로그램을 컴파일 및 조립(링크하지 않음)하는 경우

int main () { printf("Hello, world\n"); }

이동 가능한 오브젝트 파일이 될 수 있습니다.

  • A text기계 코드를 포함하는 섹션main

  • 라벨 정의main텍스트 섹션의 시작을 가리키다

  • A rodata문자열 리터럴의 바이트를 포함하는 (읽기 전용 데이터) 섹션"Hello, world\n"

  • 다음 항목에 따라 달라지는printf텍스트 섹션 중간에 있는 호출 명령의 "구멍"을 가리킵니다.

UNIX 시스템에 있는 경우 재배치 가능한 오브젝트 파일은 일반적으로 .o 파일이라고 불립니다.hello.o라벨의 정의와 용도는, 라고 하는 간단한 툴로 확인할 수 있습니다.nm라고 하는 다소 복잡한 툴로부터 보다 상세한 정보를 얻을 수 있습니다.objdump.

저는 이런 주제를 다루는 수업을 하고 학생들에게 조립기와 링커를 쓰게 합니다. 몇 주 정도 걸리지만, 그렇게 하면 대부분은 재배치 가능한 오브젝트 코드를 잘 다룰 수 있습니다.그렇게 쉬운 일이 아니야.

C학점을 수강합시다.

달릴 때gcc,clang또는 c 프로그램의 'cl'은 다음 단계를 거칩니다.

  1. 프리프로세서 토큰으로의 렉싱을 포함한 프리프로세서(#include, #ifdef, trigraph analysis, 부호화 번역, 코멘트 관리, 매크로 등)로, 최종적으로 컴파일러에 적절한 입력을 위한 플랫텍스트가 생성됩니다.
  2. 어휘 분석(토큰 및 어휘 오류 생성).
  3. 구문 분석(파싱 트리 및 구문 오류 생성)
  4. 시멘틱 분석(심볼 테이블, 스코핑 정보 및 스코핑/타이핑 오류 생성)또한 데이터 흐름, 프로그램 로직을 최적기가 조작할 수 있는 "중간 표현"으로 변환합니다.(종종 SSA). clang/LLVM은 LLVM-IR을 사용하고 GCC는 GIMPLE을 사용합니다.
  5. 프로그램 로직의 최적화(끊임없는 전파, 인라이닝, 불변수를 루프에서 끌어올리기, 자동 벡터화 등)(일반적으로 사용되는 최신 컴파일러의 코드는 대부분 최적화 패스입니다.)중간 표현을 사용한 변환은 일부 컴파일러 동작의 일부일 뿐이므로 "모든 최적화를 무효화"하는 것은 불가능하거나 의미가 없습니다.
  6. 어셈블리 소스(또는 와 같은 다른 중간 형식)로 출력합니다.NET IL 바이트 코드)
  7. 어셈블리를 바이너리 오브젝트 형식으로 조립하고 있습니다.
  8. 필요한 정적 라이브러리에 어셈블리를 링크하고 필요에 따라 어셈블리를 재배치합니다.
  9. 최종 실행 파일의 출력(elf, PE/coff, MachO64 등)

실제로는 이러한 절차 중 일부를 동시에 수행할 수 있지만 이것이 논리적인 순서입니다.대부분의 컴파일러에는 GCC와 같은 오픈 소스 컴파일러에 대한 최적화 패스 사이의 내부 표현을 덤프하는 것을 포함하여 주어진 절차(예: 전처리 또는 asm) 후에 정지하는 옵션이 있습니다.-ftree-dump-...)

DOS가 아닌 경우 실제 실행 가능한 바이너리 주변에 엘프 또는 코프 형식의 '컨테이너'가 있습니다..com실행 가능한

컴파일러에 관한 책(Dragon Book, 현장의 표준 입문서)에는 필요한 정보 등이 모두 포함되어 있습니다.

Marco가 언급했듯이 링크와 로딩은 큰 영역이며 Dragon Book은 실행 가능한 바이너리 출력에서 거의 멈춥니다.여기서 운영체제로 실제로 이행하는 것은 상당히 복잡한 프로세스이며, Linkers and Loaders의 Levine도 이에 대해 다룹니다.

나는 사람들이 오류를 수정하거나 정보를 추가할 수 있도록 이 답변을 위키에 올렸다.

C++를 바이너리 실행 파일로 변환하는 단계는 다양합니다.언어 사양에는 번역 단계가 명시되어 있지 않습니다.다만, 일반적인 번역 단계에 대해서는 설명하겠습니다.

소스 C++에서 어셈블리 또는 반복 언어

일부 컴파일러는 실제로 C++ 코드를 어셈블리 언어 또는 중간 언어로 변환합니다.이 단계는 필수 단계는 아니지만 디버깅 및 최적화에 도움이 됩니다.

어셈블리와 객체 코드 연결

다음 일반적인 단계는 어셈블리 언어를 개체 코드로 변환하는 것입니다.오브젝트 코드에는 상대 주소 및 외부 서브루틴(메서드 또는 함수)에 대한 오픈 참조가 있는 어셈블리 코드가 포함되어 있습니다.일반적으로 번역자는 가능한 한 많은 정보를 오브젝트 파일에 넣지만, 그 이외의 모든 것은 해결되지 않습니다.

오브젝트 코드 링크 중

링크 단계는 하나 이상의 객체 코드를 결합하여 참조를 확인하고 중복된 서브루틴을 제거합니다.최종 출력은 실행 파일입니다.이 파일에는 운영 체제 및 상대 주소에 대한 정보가 포함되어 있습니다.

바이너리 파일 실행

operating system은, 통상, 하드 디스크(HDD)로부터 실행 파일을 로드해, 메모리에 격납합니다.OS는 상대 주소를 물리적인 위치로 변환할 수 있습니다.OS는 실행 파일에 필요한 리소스(DLL 및 GUI 위젯 등)를 준비할 수도 있습니다(실행 파일에 기재되어 있을 수도 있습니다).

바이너리로 직접 컴파일하기 임베디드 시스템에서 사용되는 컴파일러 등 일부 컴파일러는 C++에서 실행 가능한 바이너리 코드로 직접 컴파일할 수 있습니다.이 코드에는 상대 주소 대신 물리 주소가 할당되어 있어 OS를 로드할 필요가 없습니다.

이점

이러한 국면의 장점 중 하나는 C++ 프로그램을 분할하여 개별적으로 컴파일하고 나중에 링크할 수 있다는 것입니다.다른 개발자의 작품(일명 라이브러리)과도 연계할 수 있습니다.이를 통해 개발자는 개발 중인 컴파일러 조각만 사용할 수 있으며 이미 검증된 조각으로 링크할 수 있습니다.일반적으로 C++에서 오브젝트로의 변환은 시간이 걸리는 프로세스입니다.또한 소스코드에 오류가 있을 때 모든 단계가 완료될 때까지 기다리는 것을 원하지 않습니다.

마음을 열고 항상 제3의 대안(옵션)을 기대하십시오.

이 질문에는 프로세서, 플랫폼, 어셈블러, C 컴파일러가 다르기 때문에 인텔 x86 플랫폼에 대해 설명하겠습니다.

  1. 어셈블러는 보통 순수/플랫 바이너리(raw machine code)로 조립하지 않습니다.대신 데이터, 텍스트, bss 등의 세그먼트(segment)로 정의된 파일로 조립합니다.이것은 오브젝트 파일이라고 불립니다.링커가 개입하여 세그먼트를 조정하여 실행할 수 있도록 합니다.즉, 실행할 준비가 되어 있습니다.덧붙여서 GNU를 사용하여 조립했을 때의 기본 출력은as foo.sa.out이는 Assembler Output의 줄임말입니다(단, 같은 파일명은 링커 출력의 gcc 기본값이며 어셈블러 출력은 임시일 뿐입니다).
  2. 부트 로더에는 특별한 디렉티브가 정의되어 있습니다.DOS 시절에는 다음과 같은 디렉티브를 찾을 수 있었습니다..Org 100h, which defines the assembler code to be of the old .COM variety before .EXE took over in popularity. Also, you did not need to have a assembler to produce a .COM file, using the old debug.exe that came with MSDOS, did the trick for small simple programs, the .COM files did not need a linker and were straight ready-to-run binary format. Here's a simple session using DEBUG.
1:*a 0100
2:* mov AH,07
3:* int 21
4:* cmp AL,00
5:* jnz 010c
6:* mov AH,07
7:* int 21
8:* mov AH,4C
9:* int 21
10:*
11:*r CX
12:*10
13:*n respond.com
14:*w
15:*q

This produces a ready-to-run .COM program called 'respond.com' that waits for a keystroke and not echo it to the screen. Notice, the beginning, the usage of 'a 100h' which shows that the Instruction pointer starts at 100h which is the feature of a .COM. This old script was mainly used in batch files waiting for a response and not echo it. The original script can be found here.

Again, in the case of boot loaders, they are converted to a binary format, there was a program that used to come with DOS, called EXE2BIN. That was the job of converting the raw object code into a format that can be copied on to a bootable disk for booting. Remember no linker is run against the assembled code, as the linker is for the runtime environment and sets up the code to make it runnable and executable.

The BIOS when booting, expects code to be at segment:offset, 0x7c00, if my memory serves me correct, the code (after being EXE2BIN'd), will start executing, then the bootloader relocates itself lower down in memory and continue loading by issuing int 0x13 to read from the disk, switch on the A20 gate, enable the DMA, switch onto protected mode as the BIOS is in 16bit mode, then the data read from the disk is loaded into memory, then the bootloader issues a far jump into the data code (likely to be written in C). That is in essence how the system boots.

Ok, the previous paragraph sounds abstracted and simple, I may have missed out something, but that is how it is in a nutshell.

이들은 헤더와 세그먼트로 구성된 특정 형식(Windows의 경우 COFF 등)의 파일로 컴파일되며, 그 중 일부는 "일반 바이너리" 연산 코드를 가집니다.어셈블러와 컴파일러(C 등)는 같은 종류의 출력을 생성합니다.일부 형식(예: 오래된 *).COM 파일에는 헤더는 없지만 메모리 내의 로딩 장소나 용량이 어느 정도인지 등 일정한 전제 조건이 있습니다.

Windows 머신에서는, OS 의 부스트래퍼는 BIOS 로딩된 디스크 섹터에 있습니다.이 섹터는 둘 다 「일반」입니다.OS는 로더를 로드하면 헤더와 세그먼트가 있는 파일을 읽을 수 있습니다.

도움이 되셨어요?

질문의 어셈블리 부분에 대한 답변을 드리자면 어셈블리는 제가 알고 있는 바이너리로 컴파일되지 않습니다.어셈블리 === 이진수입니다.그것은 직접 번역된다.각 어셈블리 작업에는 직접 일치하는 이진 문자열이 있습니다.각 연산에는 바이너리 코드가 있으며 각 레지스터 변수에는 바이너리 주소가 있습니다.

하지만!= 어셈블러와 제가 당신의 질문을 오해하고 있는 것이 아니라면 말입니다.

여기에 두 가지를 섞을 수 있습니다.일반적으로 다음 두 가지 토픽이 있습니다.

후자는 조립 과정에서 전자로 컴파일할 수 있다.일부 중간 형식은 어셈블되지 않고 가상 시스템에서 실행됩니다.C++의 경우 CIL로 컴파일되어 에 조립됩니다.NET 조립품이라 혼란이 좀 있습니다.

그러나 일반적으로 C와 C++는 바이너리, 즉 실행 파일 형식으로 컴파일됩니다.

당신은 읽어야 할 많은 답을 가지고 있지만, 나는 이것을 간결하게 할 수 있을 것 같다.

바이너리 코드란 마이크로프로세서의 회로를 통과하는 비트를 말합니다.마이크로프로세서는 메모리에서 각 명령을 순서대로 로드하여 그들이 말하는 모든 것을 수행합니다.x86, ARM, PowerPC 등 프로세서 패밀리에 따라 명령 형식이 다릅니다.메모리내의 명령의 주소를 지정해, 프로세서를 목적의 명령으로 지정하면, 프로세서는 프로그램의 나머지 부분에서도 즐겁게 동작합니다.

프로세서에 프로그램을 로드하려면 먼저 메모리 내에서 바이너리 코드를 액세스 가능하게 해야 합니다.이것에 주소가 할당됩니다.C 컴파일러는 파일 시스템 내의 파일을 출력합니다.이 파일은 새로운 가상 주소 공간에 로드되어야 합니다.따라서 이 파일에는 바이너리 코드와 더불어 바이너리 코드를 가지고 있다는 정보와 주소 공간의 모양을 포함해야 합니다.

부트 로더는 요구 사항이 다르므로 파일 형식이 다를 수 있습니다.하지만 이 생각은 같습니다. 바이너리 코드는 항상 더 큰 파일 형식의 페이로드입니다.적어도 올바른 명령어 세트로 작성되었는지 확인하기 위한 건전성 검사를 포함합니다.

C 컴파일러와 어셈블러는 일반적으로 정적 라이브러리 파일을 생성하도록 구성됩니다.임베디드 애플리케이션의 경우, 주소 0부터 시작하는 명령을 사용하여 원시 메모리 이미지와 같은 것을 생성하는 컴파일러를 찾을 수 있습니다.그렇지 않으면 C 컴파일러의 출력을 원하는 대로 변환하는 링커를 작성할 수 있습니다.

제가 알기론 칩셋(CPU 등)은 데이터를 저장하기 위한 레지스터 세트를 가지고 있으며, 이러한 레지스터를 조작하기 위한 일련의 명령을 이해하고 있습니다.지시사항은 '이 값을 이 레지스터에 저장', '이 값을 이동' 또는 '이 두 값을 비교'와 같습니다.이러한 명령어는 종종 칩셋이 인식하는 숫자에 매핑된 인간이 학습할 수 있는 짧은 알파벳 코드(어셈블리 언어 또는 어셈블리러)로 표현됩니다. 이 숫자들은 2진수(기계 코드)로 칩에 표시됩니다.

이 코드들은 소프트웨어의 최저 레벨입니다.그 이상으로 깊이 들어가면, 실제의 칩의 아키텍쳐(architecture)에 들어갈 수 있습니다.이것은 제가 관여하지 않은 것입니다.

PE 로더가 메모리에 없기 때문에 실행 파일(윈도우즈에서는 PE 형식)을 사용하여 시스템을 부팅할 수 없습니다.

부트스트래핑은 디스크상의 마스터 부트레코드에 수백 바이트의 코드가 포함되어 있습니다.컴퓨터의 BIOS(마더보드상의 ROM내)는, 이 BLOB 를 메모리에 로드해, CPU 명령 포인터를 이 부트 코드의 선두에 설정합니다.

The boot code then loads a "second stage" loader, on Windows called NTLDR (no extension) from the root directory. This is raw machine code that, like the MBR loader, is loaded into memory cold and executed.

NTLDR has the full capability to load PE files including DLLs and drivers.

С(++) (unmanaged) really compiles to plain binary. Some OS-related stuff - are BIOS and OS function calls, they're different for each OS, but still binary.
1. Assembler compiles to pure binary, but, as strange as it gets, it is less optimized than C(++)
2. OS kernel, as well as bootloader, also written in C, so no problems here.

Java, Managed C++, and other .NET stuff, compiles into some pseudocode (MSIL in .NET), which makes it cross-OS and cross-platform, but requires local interpreter or translator to run.

ReferenceURL : https://stackoverflow.com/questions/2135788/what-do-c-and-assembler-actually-compile-to

반응형