컴퓨터에 설치한 경우 바이러스 백신 프로그램할 수 있다 컴퓨터의 모든 파일은 물론 각 파일을 개별적으로 검사합니다.. 파일을 마우스 오른쪽 버튼으로 클릭하고 파일에 바이러스가 있는지 검사하는 적절한 옵션을 선택하여 모든 파일을 검사할 수 있습니다.

예를 들어, 이 그림에서는 강조 표시되어 있습니다. 파일 my-file.elf, 그런 다음 이 파일을 마우스 오른쪽 버튼으로 클릭하고 파일 메뉴에서 옵션을 선택해야 합니다. "AVG로 스캔". 이 옵션을 선택하면 AVG Antivirus가 파일을 열고 바이러스를 검사합니다.


결과적으로 오류가 발생하는 경우도 있습니다. 잘못된 소프트웨어 설치, 이는 설치 프로세스 중에 발생한 문제로 인해 발생할 수 있습니다. 이는 운영 체제를 방해할 수 있습니다. ELF 파일을 올바른 응용 프로그램 소프트웨어에 연결하십시오, 소위 영향을 미치는 "파일 확장자 연결".

때로는 단순하다 Dolphin(에뮬레이터) 재설치 ELF를 Dolphin(에뮬레이터)과 올바르게 연결하면 문제를 해결할 수 있습니다. 다른 경우에는 파일 연결 문제로 인해 발생할 수 있습니다. 나쁜 소프트웨어 프로그래밍개발자이며 추가 지원을 받으려면 개발자에게 문의해야 할 수도 있습니다.


조언:최신 패치와 업데이트를 받을 수 있도록 Dolphin(에뮬레이터)을 최신 버전으로 업데이트해 보세요.


이는 너무 뻔해 보일 수도 있지만, 종종 ELF 파일 자체가 문제의 원인일 수 있습니다.. 이메일 첨부파일을 통해 파일을 받거나 웹사이트에서 다운로드한 후 다운로드 프로세스가 중단된 경우(예: 정전 또는 기타 사유) 파일이 손상될 수 있습니다. 가능하다면 ELF 파일의 새 복사본을 구해 다시 열어보세요.


주의하여:손상된 파일은 PC에 있는 이전 또는 기존 맬웨어에 부수적인 손상을 일으킬 수 있으므로 최신 바이러스 백신을 사용하여 컴퓨터를 최신 상태로 유지하는 것이 중요합니다.


파일이 ELF인 경우 컴퓨터의 하드웨어와 관련된필요할 수 있는 파일을 열려면 장치 드라이버 업데이트이 장비와 관련이 있습니다.

이 문제 일반적으로 미디어 파일 형식과 관련됨, 이는 컴퓨터 내부의 하드웨어를 성공적으로 여는 데 달려 있습니다. 사운드 카드 또는 비디오 카드. 예를 들어, 오디오 파일을 열려고 하는데 열 수 없는 경우 다음을 수행해야 할 수 있습니다. 사운드 카드 드라이버 업데이트.


조언: ELF 파일을 열려고 할 때 다음과 같은 메시지가 표시되는 경우 .SYS 파일 오류 메시지, 문제는 아마도 손상되거나 오래된 장치 드라이버와 관련됨업데이트해야 합니다. DriverDoc과 같은 드라이버 업데이트 소프트웨어를 사용하면 이 프로세스를 더 쉽게 만들 수 있습니다.


단계를 수행해도 문제가 해결되지 않는 경우 ELF 파일을 여는 데 여전히 문제가 있습니다. 그 이유는 다음과 같습니다. 사용 가능한 시스템 리소스 부족. ELF 파일의 일부 버전을 컴퓨터에서 제대로 열려면 상당한 양의 리소스(예: 메모리/RAM, 처리 능력)가 필요할 수 있습니다. 이 문제는 상당히 오래된 컴퓨터 하드웨어와 동시에 훨씬 새로운 운영 체제를 사용하는 경우 매우 일반적입니다.

이 문제는 운영 체제(및 백그라운드에서 실행되는 기타 서비스)가 작동하지 않아 컴퓨터가 작업을 따라잡는 데 문제가 있을 때 발생할 수 있습니다. ELF 파일을 여는 데 너무 많은 리소스를 소비합니다.. Nintendo Wii 게임 파일을 열기 전에 PC의 모든 애플리케이션을 닫아보세요. 컴퓨터에서 사용 가능한 모든 리소스를 확보하면 ELF 파일을 열려고 시도할 수 있는 가장 좋은 위치에 있게 됩니다.


만약 너라면 위에 설명된 모든 단계를 완료했습니다. ELF 파일이 여전히 열리지 않으면 다음을 실행해야 할 수도 있습니다. 장비 업데이트. 대부분의 경우 이전 버전의 하드웨어를 사용하더라도 처리 능력은 대부분의 사용자 응용 프로그램에 충분할 수 있습니다(3D 렌더링, 금융/과학 모델링 또는 CPU 집약적인 작업을 많이 수행하지 않는 한). 집중 멀티미디어 작업) . 따라서, 컴퓨터에 메모리가 부족할 가능성이 높습니다.(일반적으로 "RAM" 또는 랜덤 액세스 메모리라고 함) 파일을 여는 작업을 수행합니다.

ELF 파일 문제를 해결하는 데 도움이 되었기를 바랍니다. 목록에서 애플리케이션을 어디에서 다운로드할 수 있는지 모르는 경우 링크(프로그램 이름)를 클릭하세요. 필수 애플리케이션의 보안 설치 버전을 다운로드할 수 있는 위치에 대한 자세한 정보를 찾을 수 있습니다.

이 페이지를 방문하면 다음과 같은 질문이나 이와 유사한 질문에 구체적으로 답하는 데 도움이 될 것입니다.

  • ELF 확장자를 가진 파일을 여는 방법은 무엇입니까?
  • ELF 파일을 다른 형식으로 변환하는 방법은 무엇입니까?
  • ELF 파일 형식 확장자는 무엇입니까?
  • ELF 파일을 지원하는 프로그램은 무엇입니까?

이 페이지의 자료를 본 후에도 위에 제시된 질문에 대해 여전히 만족스러운 답변을 얻지 못했다면 이는 ELF 파일에 대해 여기에 제시된 정보가 불완전하다는 것을 의미합니다. 문의 양식을 사용하여 문의하고 찾지 못한 정보를 적어주세요.

또 무엇이 문제를 일으킬 수 있나요?

ELF 파일을 열 수 없는 데는 더 많은 이유가 있을 수 있습니다(단지 해당 응용 프로그램이 없기 때문만은 아닙니다).
첫째로- ELF 파일이 이를 지원하기 위해 설치된 애플리케이션과 잘못 링크(호환되지 않음)되었을 수 있습니다. 이 경우에는 이 연결을 직접 변경해야 합니다. 이렇게 하려면 편집하려는 ELF 파일을 마우스 오른쪽 버튼으로 클릭하고 옵션을 클릭하세요. "열기 위해"그런 다음 목록에서 설치한 프로그램을 선택합니다. 이 작업을 수행하면 ELF 파일을 열 때 발생하는 문제가 완전히 사라집니다.
둘째- 열려고 하는 파일이 단순히 손상되었을 수도 있습니다. 이 경우 새 버전을 찾거나 동일한 소스에서 다시 다운로드하는 것이 가장 좋습니다(아마도 이전 세션에서 어떤 이유로 ELF 파일 다운로드가 완료되지 않아 올바르게 열 수 없었을 수 있습니다). .

도와주고 싶나요?

ELF 파일 확장자에 대한 추가 정보가 있는 경우 당사 사이트 사용자와 공유해 주시면 감사하겠습니다. 아래 양식을 사용하여 ELF 파일에 대한 정보를 보내주십시오.

ELF 형식

ELF 형식에는 실행 파일이나 개체 파일과 같이 지금까지 다르게 호출한 여러 파일 형식이 있습니다. 그러나 ELF 표준은 다음 유형을 구별합니다.

1. 이동할 파일(재배치 가능한 파일) 다른 개체 파일에 연결할 수 있는 명령과 데이터를 저장합니다. 이러한 연결의 결과는 실행 파일 또는 공유 개체 파일이 될 수 있습니다.

2. 공유 객체 파일(공유 객체 파일)에는 명령과 데이터도 포함되어 있지만 두 가지 방법으로 사용할 수 있습니다. 첫 번째 경우에는 다른 재배치 가능한 파일 및 공유 개체 파일에 연결되어 새 개체 파일이 생성될 수 있습니다. 두 번째 경우, 실행을 위해 프로그램이 시작되면 운영 체제는 해당 프로그램을 프로그램의 실행 파일과 동적으로 연결할 수 있으며, 그 결과 프로그램의 실행 가능 이미지가 생성됩니다. 후자의 경우 우리는 공유 라이브러리에 대해 이야기하고 있습니다.

3. 실행 가능 파일시스템이 프로세스의 이미지를 생성할 수 있도록 하는 완전한 설명을 저장합니다. 여기에는 필요한 공유 객체 파일에 대한 지침, 데이터, 설명, 필요한 기호 및 디버깅 정보가 포함되어 있습니다.

그림에서. 2.4는 운영 체제가 프로그램 이미지를 생성하고 실행을 위해 프로그램을 시작할 수 있는 실행 파일의 구조를 보여줍니다.

쌀. 2.4. ELF 형식의 실행 파일 구조

헤더는 파일에서 고정된 위치를 갖습니다. 나머지 구성 요소는 헤더에 저장된 정보에 따라 배치됩니다. 따라서 헤더에는 파일 구조, 개별 구성 요소의 위치 및 크기에 대한 일반적인 설명이 포함됩니다.

ELF 파일의 헤더는 파일의 구조를 결정하므로 좀 더 자세히 살펴보겠습니다(표 2.4).

표 2.3. ELF 헤더 필드

필드 설명
e_ident 파일 형식(ELF), 버전 번호, 시스템 아키텍처(32비트 또는 64비트) 등 파일의 일반적인 특성을 각각 정의하는 바이트 배열입니다.
e_type 파일 유형(ELF 형식은 여러 유형을 지원하므로)
e_machine 이 파일이 생성된 하드웨어 플랫폼의 아키텍처입니다. 테이블에 2.4는 이 필드의 가능한 값을 보여줍니다.
e_version ELF 형식 버전 번호. 일반적으로 최신 버전을 의미하는 EV_CURRENC(현재)로 정의됩니다.
e_entry 프로그램을 로드한 후 시스템이 제어권을 전달할 가상 주소(진입점)
e_phoff 프로그램 헤더 테이블의 위치(파일 시작 부분에서 오프셋)
e_shoff 섹션 헤더 테이블 위치
e_ehsize 헤더 크기
e_phentsize 각 프로그램 헤더의 크기
e_phnum 프로그램 헤더 수
e_shentsize 각 세그먼트 헤더(섹션)의 크기
e_shnum 세그먼트 헤더(섹션) 수
e_shstrndx 스트링 테이블을 포함하는 세그먼트의 위치

표 2.4. ELF 파일 헤더 e_machine 필드 값

의미 하드웨어 플랫폼
EM_M32 AT&T 우리 32100
EM_SPARC 썬 스팍
EM_386 인텔 80386
EM_68K 모토로라 68000
EM_88K 모토로라 88000
EM_486 인텔 80486
EM_860 인텔 i860
EM_MIPS MIPS RS3000 빅엔디안
EM_MIPS_RS3_LE MIPS RS3000 리틀엔디안
EM_RS6000 RS6000
EM_PA_RISC PA-RISC
EM_nCUBE 엔큐브
EM_VPP500 후지쯔 VPP500
EM_SPARC32PLUS 썬 SPARC 32+

프로그램 헤더 테이블에 포함된 정보는 세그먼트에서 프로세스 이미지를 생성하는 방법을 커널에 알려줍니다. 대부분의 세그먼트는 메모리에 복사(매핑)되며 코드 또는 데이터 세그먼트와 같이 실행되는 프로세스의 관련 세그먼트를 나타냅니다.

각 프로그램 세그먼트 헤더는 하나의 세그먼트를 설명하며 다음 정보를 포함합니다.

이 세그먼트에 대한 세그먼트 유형 및 운영 체제 작업

파일의 세그먼트 위치

프로세스 가상 메모리에 있는 세그먼트의 시작 주소

파일의 세그먼트 크기

메모리 세그먼트 크기

세그먼트 액세스 플래그(쓰기, 읽기, 실행)

일부 세그먼트는 실행을 위해 프로그램을 시작할 때 커널에 지시하여 이러한 세그먼트에 해당하는 데이터 구조를 생성하도록 지시하는 LOAD 유형입니다. 지역, 프로세스의 가상 메모리와 관련 속성의 연속 섹션을 정의합니다. ELF 파일의 위치가 해당 프로그램 헤더에 표시된 세그먼트는 생성된 영역에 매핑되며 시작 부분의 가상 주소도 프로그램 헤더에 표시됩니다. 이 유형의 세그먼트에는 예를 들어 프로그램 명령(코드) 및 해당 데이터가 포함된 세그먼트가 포함됩니다. 세그먼트 크기가 영역 크기보다 작은 경우 사용되지 않은 공간은 0으로 채워질 수 있습니다. 이 메커니즘은 특히 초기화되지 않은 프로세스 데이터(BSS)를 생성할 때 사용됩니다. 영역에 대해서는 3장에서 더 자세히 설명하겠습니다.

INTERP 세그먼트는 프로그램 해석기를 저장합니다. 이 세그먼트 유형은 동적 연결이 필요한 프로그램에 사용됩니다. 동적 연결의 핵심은 실행 파일(공유 개체 파일)의 개별 구성 요소가 컴파일 단계가 아니라 실행을 위해 프로그램을 시작하는 단계에서 연결된다는 것입니다. 해당 파일의 이름은 동적 링크 편집기, 이 세그먼트에 저장됩니다. 프로그램이 실행을 위해 시작되면 커널은 지정된 링크 편집기를 사용하여 프로세스 이미지를 생성합니다. 따라서 처음에 메모리에 로드되는 것은 소스 프로그램이 아니라 동적 링크 편집기입니다. 다음 단계에서는 동적 링크 편집기가 UNIX 커널과 함께 작동하여 실행 파일의 전체 이미지를 만듭니다. 동적 편집기는 이름이 소스 실행 파일의 별도 세그먼트에 저장되어 있는 필수 공유 개체 파일을 로드하고 필요한 배치 및 연결을 수행합니다. 마지막으로 제어권이 원본 프로그램으로 이전됩니다.

마지막으로 파일은 헤더 테이블로 끝납니다. 섹션또는 섹션(부분). 섹션은 컴파일 또는 동적 연결 중에 다른 모듈과 연결하는 데 사용되는 파일 섹션을 정의합니다. 따라서 제목에는 이러한 섹션을 설명하는 데 필요한 모든 정보가 포함되어 있습니다. 일반적으로 섹션에는 세그먼트에 대한 더 자세한 정보가 포함됩니다. 예를 들어, 코드 세그먼트는 프로그램에서 사용되는 기호의 인덱스를 저장하는 해시 테이블, 프로그램의 초기화 코드 섹션, 동적 편집기에서 사용되는 연결 테이블 및 실제 코드를 포함하는 섹션과 같은 여러 섹션으로 구성될 수 있습니다. 프로그램 지침.

프로세스 가상 메모리의 구성을 논의할 때 3장에서 ELF 형식으로 돌아가지만 지금은 다음 일반적인 형식인 COFF로 넘어갈 것입니다.

The Art of 프로그래밍 for Unix 책에서 작가 레이먼드 에릭 스티븐

컴퓨터 작업을 위한 자체 사용 설명서 책에서 작가 콜리스니첸코 데니스 니콜라예비치

책 초록, 교과 과정, 컴퓨터 졸업장에서 작가 Balovsyak Nadezhda Vasilievna

5.2.6. Windows INI 형식 많은 Microsoft Windows 프로그램은 예제 5.6에 표시된 것과 유사한 텍스트 데이터 형식을 사용합니다. 이 예에서는 account,directory,numeric_id,developer라는 선택적 리소스를 python, sng, fetchmail 및 py-howto라는 프로젝트와 연결합니다. 녹음 중

책에서 컴퓨터 작업을 위한 최신 자체 사용 설명서 작가 벨룬초프 발레리

14.5.3. 셀 형식 형식은 셀 값이 표시되는 방식을 지정합니다. 형식은 셀의 데이터 유형과 밀접한 관련이 있습니다. 유형은 직접 설정하세요. 숫자를 입력한 경우 숫자 데이터 유형입니다. Excel 자체에서는 데이터 유형에 따라 형식을 결정하려고 합니다. 예를 들어 텍스트를 입력했다면

The Art of 프로그래밍 for Unix 책에서 작가 레이먼드 에릭 스티븐

PDF 형식 PDF는 Portable Document Format의 약자입니다. 이 형식은 파일에 정보를 표시할 때 발생하는 문제를 제거하기 위해 특별히 만들어졌습니다. 장점은 첫째, PDF 형식으로 저장된 문서가 동일하다는 것입니다.

TCP/IP 아키텍처, 프로토콜, 구현(IP 버전 6 및 IP 보안 포함) 책에서 발췌 by 믿음 시드니 M

파일 형식 사용자가 파일 작업을 시작하면 시스템은 파일이 어떤 형식으로 작성되었는지, 어떤 프로그램을 사용하여 열어야 하는지 알아야 합니다. 예를 들어 파일에 일반 텍스트가 포함되어 있으면 모든 텍스트 프로그램에서 읽을 수 있습니다.

모두를 위한 Yandex 책에서 저자 Abramzon M. G.

5.2.2. RFC 822 형식 RFC 822 메타형식은 인터넷 전자 메일 메시지의 텍스트 형식에서 파생됩니다. RFC 822는 이 형식을 설명하는 주요 인터넷 RFC 표준입니다(나중에 RFC 2822로 대체됨). MIME(다목적 인터넷 미디어 확장) 형식

Macromedia Flash Professional 8 책에서. 그래픽 및 애니메이션 저자 Dronov V. A.

5.2.3. Cookie-Jar 형식 Cookie-jar 형식은 Fortune(1)에서 자체 임의 인용문 데이터베이스를 위해 사용됩니다. 단순히 구조화되지 않은 텍스트 블록인 게시물에 적합합니다. 기호는 이 형식에서 레코드 구분 기호로 사용됩니다.

컴퓨터 사운드 처리 책에서 작가 Zagumennov 알렉산더 페트로비치

5.2.4. Record-jar 형식 쿠키-jar 형식 레코드 구분 기호는 레코드에 대한 RFC 822 메타포 형식과 잘 작동하여 이 책에서 "record-jar"라는 형식을 형성합니다. 때로는 명시적 이름이 다른 여러 항목을 지원하는 텍스트 형식이 필요한 경우도 있습니다.

UNIX 운영 체제 책에서 작가 Robachevsky 안드레이 M.

5.2.6. Windows INI 형식 많은 Microsoft Windows 프로그램은 예제 5.6에 표시된 것과 유사한 텍스트 데이터 형식을 사용합니다. 이 예에서는 account,directory,numeric_id,developer라는 선택적 리소스를 python, sng, fetchmail 및 py-howto라는 프로젝트와 연결합니다. 녹음 중

여성용 사무용 컴퓨터 책에서 작가 파스테르나크 예브게니아

19.5 일반화된 URL 형식 위 내용을 요약하면 다음과 같습니다. URL은 사용된 액세스 프로토콜로 시작됩니다.? 온라인 뉴스와 이메일을 제외한 모든 애플리케이션의 경우 다음 구분 기호가 뒤에 옵니다: //.? 그런 다음 서버의 호스트 이름이 지정됩니다.? 마지막으로

작가의 책에서

3.3.1. RSS 형식 웹사이트 뉴스를 다양한 방식으로 읽을 수 있습니다. 가장 쉬운 방법은 수시로 사이트를 방문하여 새 메시지를 보는 것입니다. 뉴스 채널에 연결하여 자체적으로 헤드라인이나 뉴스 요약을 수신하는 프로그램을 설치할 수 있습니다.

작가의 책에서

MP3 형식 MP3 형식은 MPEG 1 레벨 3 코덱으로 압축된 음악 파일을 배포하기 위해 만들어졌으며 현재 인터넷뿐만 아니라 인터넷을 통해 음악을 배포하는 데 가장 널리 사용되는 형식입니다. 모든 오디오 녹음 및 처리 프로그램에서 지원됩니다.

작가의 책에서

MP3 형식 인식 가능한 오디오 코딩을 기반으로 하는 국제 기구 MPEG(Moving Pictures Experts Group)에서 제안한 오디오 압축 방법이자 압축 오디오 파일 형식입니다. 효율적인 코딩 알고리즘 생성 작업

작가의 책에서

ELF 형식 ELF 형식에는 여러 가지 파일 형식이 있으며 지금까지 실행 파일이나 개체 파일과 같은 다른 이름으로 불렀습니다. 그러나 ELF 표준은 다음 유형을 구별합니다.1. 지시사항과 데이터를 저장하는 재배치 가능한 파일입니다.

작가의 책에서

숫자 형식 드디어 숫자 형식에 도달했습니다. 이미 여러 번 언급했지만 이제 자세히 설명하겠습니다(일반적인 의미는 이미 이해하셨겠지만). Excel의 숫자는 다양한 형식으로 표시될 수 있습니다. 이 섹션에서는 존재하는 숫자 형식과 방법에 대해 설명합니다.

이번 리뷰에서는 아직 64비트 버전이 필요하지 않기 때문에 이 형식의 32비트 버전에 대해서만 설명하겠습니다.

모든 ELF 파일(이 형식의 개체 모듈 포함)은 다음 부분으로 구성됩니다.

  • ELF 파일 헤더;
  • 프로그램 섹션 표(객체 모듈에는 없을 수 있음)
  • ELF 파일 섹션;
  • 섹션 테이블(실행 모듈에 없을 수 있음)
  • 성능상의 이유로 ELF 형식은 비트 필드를 사용하지 않습니다. 그리고 모든 구조는 일반적으로 4바이트로 정렬됩니다.

이제 ELF 파일 헤더에 사용되는 유형을 살펴보겠습니다.

이제 파일 헤더를 살펴보겠습니다.

#define EI_NIDENT 16 struct elf32_hdr ( unsigned char e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; /* 진입점 */ Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_fla gs; e_ehsize; Elf32_Half e_shnum;

e_ident 배열은 시스템에 대한 정보를 포함하며 여러 하위 필드로 구성됩니다.

구조체( unsigned char ei_magic; unsigned char ei_class; unsigned char ei_data; unsigned char ei_version; unsigned char ei_pad; )

  • ei_magic - 모든 ELF 파일에 대한 상수 값, (0x7f, "E", "L", "F")와 같음
  • ei_class - ELF 파일 클래스(1~32비트, 2~64비트는 고려하지 않음)
  • ei_data - 이 파일의 바이트 순서를 결정합니다. 이 순서는 플랫폼에 따라 다르며 정방향(LSB 또는 1) 또는 역방향(MSB 또는 2)일 수 있습니다. Intel 프로세서의 경우 값 1만 허용됩니다.
  • ei_version은 다소 쓸모 없는 필드이며, 1(EV_CURRENT)이 아니면 파일이 잘못된 것으로 간주됩니다.

운영 체제는 ei_pad 필드에 식별 정보를 저장합니다. 이 필드는 비어 있을 수 있습니다. 그것은 우리에게도 중요하지 않습니다.

e_type 헤더 필드에는 여러 값이 포함될 수 있습니다. 실행 파일의 경우 ET_EXEC가 2와 같아야 합니다.

e_machine - 이 실행 파일이 실행될 수 있는 프로세서를 결정합니다. (우리의 경우 허용되는 값 EM_386은 3입니다.)

e_version 필드는 헤더의 ei_version 필드에 해당합니다.

e_entry 필드는 프로그램이 시작되기 전에 eip에 배치되는 프로그램의 시작 주소를 정의합니다.

e_phoff 필드는 프로그램을 메모리에 로드하는 데 사용되는 프로그램 섹션 테이블이 위치한 파일의 시작 부분으로부터의 오프셋을 지정합니다.

모든 필드의 목적을 나열하지는 않겠습니다. 모든 필드가 로드에 필요한 것은 아닙니다. 두 가지만 더 설명하겠습니다.

e_phentsize 필드는 프로그램 섹션 테이블의 항목 크기를 지정합니다.

그리고 e_phnum 필드는 프로그램 섹션 테이블의 항목 수를 결정합니다.

프로그램 섹션이 아닌 섹션 테이블은 프로그램을 연결하는 데 사용됩니다. 우리는 그것을 고려하지 않을 것입니다. 또한 동적으로 연결된 모듈은 고려하지 않습니다. 이 주제는 매우 복잡하며 처음 아는 사람에게는 적합하지 않습니다. :)

이제 프로그램 섹션에 대해 설명합니다. 프로그램 섹션 테이블 항목 형식은 다음과 같습니다.

Struct elf32_phdr ( Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; );

필드에 대해 자세히 알아보세요.

  • p_type - 프로그램 섹션의 유형을 결정합니다. 여러 값을 가질 수 있지만 우리는 하나만 관심이 있습니다. PT_LOAD(1). 섹션이 이 유형이면 메모리에 로드됩니다.
  • p_offset - 이 섹션이 시작되는 파일의 오프셋을 결정합니다.
  • p_vaddr - 이 섹션이 메모리에 로드되어야 하는 가상 주소를 정의합니다.
  • p_paddr - 이 섹션이 로드되어야 하는 물리적 주소를 정의합니다. 이 필드는 선택 사항이며 일부 플랫폼에서만 의미가 있습니다.
  • p_filesz - 파일의 섹션 크기를 결정합니다.
  • p_memsz - 메모리 섹션의 크기를 결정합니다. 이 값은 이전 값보다 클 수 있습니다. p_flag 필드는 메모리의 섹션에 대한 액세스 유형을 결정합니다. 일부 섹션은 수행할 수 있고 일부 섹션은 기록할 수 있습니다. 모두 기존 시스템에서 읽을 수 있습니다.

ELF 형식을 로드하는 중입니다.

제목을 조금 알아냈습니다. 이제 ELF 형식의 바이너리 파일을 로드하는 알고리즘을 제공하겠습니다. 알고리즘은 개략적이므로 작업 프로그램으로 간주되어서는 안 됩니다.

Int LoadELF (unsigned char *bin) ( struct elf32_hdr *EH = (struct elf32_hdr *)bin; struct elf32_phdr *EPH; if (EH->e_ident != 0x7f || // 제어 MAGIC EH->e_ident != "E" || EH->e_ident != "L" || EH->e_ident != "F" || EH->e_ident != ELFCLASS32 || // EH->e_ident != ELFDATA2LSB || e_ident != EV_CURRENT || // 버전 EH->e_type != ET_EXEC || // EH->e_machine != EM_386 || // 플랫폼 EH->e_version != EV_CURRENT) // 그리고 다시 한 번 버전을 입력합니다. return ELF_WRONG; EPH = (struct elf32_phdr *)(bin + EH->e_phoff); while (EH->e_phnum--) ( if (EPH->p_type == PT_LOAD) memcpy (EPH->p_vaddr, bin + EPH- >p_offset, EPH->p_filesz); EPH = (struct elf32_phdr *)((unsigned char *)EPH + EH->e_phentsize)); )

진지하게, EPH->p_flags 필드를 분석하고 해당 페이지에 대한 액세스 권한을 설정하는 것이 가치가 있으며 단순히 복사하는 것은 여기에서 작동하지 않지만 이는 더 이상 형식과 관련이 없고 메모리 할당과 관련이 있습니다. 그러므로 지금은 이에 대해 이야기하지 않겠습니다.

PE 형식.

여러 면에서 ELF 형식과 유사하며 당연히 다운로드할 수 있는 섹션도 있습니다.

Microsoft의 다른 모든 것과 마찬가지로 PE 형식은 EXE 형식을 기반으로 합니다. 파일 구조는 다음과 같습니다.

  • 00h - EXE 헤더(보지 않겠습니다. Dos만큼 오래되었네요. :)
  • 20h - OEM 헤더(중요한 내용 없음)
  • 3сh - 파일의 실제 PE 헤더 오프셋(dword).
  • 스터브 이동 테이블;
  • 그루터기;
  • PE 헤더;
  • 객체 테이블;
  • 파일 객체;

stub은 리얼 모드에서 실행되고 몇 가지 예비 작업을 수행하는 프로그램입니다. 없을 수도 있지만 때로는 필요할 수도 있습니다.

우리는 PE 헤더라는 약간 다른 것에 관심이 있습니다.

그 구조는 다음과 같습니다.

구조체 pe_hdr( unsigned long pe_sign; unsigned short pe_cputype; unsigned short pe_objnum; unsigned long pe_time; unsigned long pe_cofftbl_off; unsigned long pe_cofftbl_size; unsigned short pe_nthdr_size; unsigned short pe_flags; unsigned short pe_magic; unsigned short pe_link_ver; unsigned long pe_code_s size; 에드 오래 pe_idata_size ; unsigned long pe_udata_size; unsigned long pe_obj_align; // 기타 중요하지 않은 사항이 많습니다.

거기에는 많은 것들이 있습니다. 이 헤더의 크기는 248바이트라고만 말하면 충분합니다.

그리고 가장 중요한 것은 이러한 필드의 대부분이 사용되지 않는다는 것입니다. (누가 이렇게 빌드합니까?) 아니요, 물론 그들은 꽤 잘 알려진 목적을 가지고 있지만 예를 들어 내 테스트 프로그램의 pe_code_base, pe_code_size 등 필드에 0이 포함되어 있지만 잘 작동합니다. 결론은 객체 테이블을 기준으로 파일이 로드된다는 것입니다. 그것이 우리가 이야기할 것입니다.

객체 테이블은 PE 헤더 바로 뒤에 옵니다. 이 테이블의 항목은 다음 형식을 갖습니다.

Struct pe_ohdr( unsigned char o_name; unsigned long o_vsize; unsigned long o_vaddr; unsigned long o_psize; unsigned long o_poff; unsigned char o_reserved; unsigned long o_flags; );

  • o_name - 섹션의 이름이며 로딩과 전혀 무관합니다.
  • o_vsize - 메모리 섹션의 크기;
  • o_vaddr - ImageBase에 상대적인 메모리 주소입니다.
  • o_psize - 파일의 섹션 크기;
  • o_poff - 파일의 섹션 오프셋;
  • o_flags - 섹션 플래그;

깃발을 자세히 살펴볼 가치가 있습니다.

  • 00000004h - 16비트 오프셋이 있는 코드에 사용됩니다.
  • 00000020h - 코드 섹션
  • 00000040h - 초기화된 데이터 섹션
  • 00000080h - 초기화되지 않은 데이터 섹션
  • 00000200h - 댓글 또는 기타 유형의 정보
  • 00000400h - 오버레이 섹션
  • 00000800h - 프로그램 이미지의 일부가 아닙니다.
  • 00001000h - 일반 데이터
  • 00500000h - 별도로 지정하지 않는 한 기본 정렬
  • 02000000h - 메모리에서 언로드 가능
  • 04000000h - 캐시되지 않음
  • 08000000h - 페이징되지 않음
  • 10000000h - 공유
  • 20000000h - 가능
  • 40000000h - 읽을 수 있음
  • 80000000h - 쓸 수 있어요

다시 말하지만, 공유 및 오버레이 섹션에 대해서는 이야기하지 않겠습니다. 우리는 코드, 데이터 및 액세스 권한에 관심이 있습니다.

일반적으로 이 정보는 이미 바이너리 파일을 다운로드하기에 충분합니다.

PE 형식을 로드하는 중입니다.

int LoadPE (unsigned char *bin) ( struct elf32_hdr *PH = (struct pe_hdr *) (bin + *((unsigned long *)&bin)); // 물론 조합이 명확하지 않습니다. 그냥 dword를 사용하세요 at offset 0x3c / / 그리고 파일 이미지의 PE 헤더 주소를 계산합니다 struct elf32_phdr *POH; if (PH == NULL || // PH->pe_sign 포인터 제어 != 0x4550 || // PE 서명 ("P" , "E", 0, 0) PH->pe_cputype != 0x14c || // i386 (PH->pe_flags & 2) == 0) // 파일을 실행할 수 없습니다! return PE_WRONG; >pe_obj_num--) ( if ((POH->p_flags & 0x60) != 0) // 코드 또는 초기화된 데이터 memcpy (PE->pe_image_base + POH->o_vaddr, bin + POH- >o_poff, POH->o_psize ); POH = (struct pe_ohdr *)((unsigned char *)POH + sizeof (struct pe_ohdr));

이것도 기성 프로그램이 아니라 로딩 알고리즘입니다.

그리고 다시 말하지만, 주제의 범위를 벗어나기 때문에 다루지 않은 많은 사항이 있습니다.

하지만 이제는 기존 시스템 기능에 대해 조금 이야기할 가치가 있습니다.

시스템 기능.

프로세서에서 사용할 수 있는 보호 도구의 유연성(설명자 테이블 수준 보호, 세그먼트 수준 보호, 페이지 수준 보호)에도 불구하고 기존 시스템(Windows 및 Unix 모두)에서는 페이지 보호만 완전히 사용됩니다. 코드가 작성되는 것을 방지할 수는 있지만 데이터가 실행되는 것을 방지할 수는 없습니다. (아마 이것이 시스템 취약점이 많은 이유일까요?)

모든 세그먼트는 선형 주소 0부터 주소가 지정되고 선형 메모리 끝까지 확장됩니다. 프로세스 구분은 페이지 테이블 수준에서만 이루어집니다.

이와 관련하여 모든 모듈은 시작 주소가 아니라 세그먼트에서 충분히 큰 오프셋을 사용하여 연결됩니다. Windows에서 세그먼트의 기본 주소는 0x400000이고 Unix(Linux 또는 FreeBSD)에서는 - 0x8048000입니다.

일부 기능은 메모리의 페이지 구성과도 관련이 있습니다.

ELF 파일은 섹션의 경계와 크기가 파일의 4KB 블록 내에 속하도록 연결됩니다.

그리고 PE 형식에서는 형식 자체가 512바이트 섹션을 정렬할 수 있다는 사실에도 불구하고 4k에서 섹션 정렬이 사용됩니다. Windows에서는 더 작은 정렬이 올바른 것으로 간주되지 않습니다.

표준 개발 도구는 디버깅 정보를 포함하는 기능을 통해 프로그램을 ELF(Executable and Linkable Format) 파일로 컴파일합니다. 형식 사양을 읽을 수 있습니다. 또한 각 아키텍처에는 ARM 기능과 같은 고유한 기능이 있습니다. 이 형식을 간단히 살펴보겠습니다.
ELF 형식의 실행 파일은 다음 부분으로 구성됩니다.
1. 헤더(ELF 헤더)
파일 및 주요 특성에 대한 일반 정보가 포함되어 있습니다.
2. 프로그램 헤더 테이블
이것은 파일 섹션과 메모리 세그먼트 사이의 대응표입니다. 이는 부트로더에게 각 섹션을 쓸 메모리 영역을 알려줍니다.
3. 섹션
섹션에는 파일의 모든 정보(프로그램, 데이터, 디버깅 정보 등)가 포함됩니다.
각 섹션에는 유형, 이름 및 기타 매개변수가 있습니다. ".text" 섹션에는 일반적으로 코드 ".symtab" - 프로그램 기호(파일 이름, 프로시저 및 변수 이름) 테이블, ".strtab" - 문자열 테이블, ".debug_" 접두사가 있는 섹션 -이 저장됩니다. 디버깅 정보 등 .d. 또한 파일에는 인덱스가 0인 빈 섹션이 있어야 합니다.
4. 섹션 헤더 테이블
이것은 섹션 헤더의 배열을 포함하는 테이블입니다.
형식은 ELF 생성 섹션에서 자세히 설명합니다.

드워프 개요

DWARF는 표준화된 디버깅 정보 형식입니다. 표준은 공식 홈페이지에서 다운로드할 수 있습니다. 형식에 대한 간략한 개요도 있습니다: DWARF 디버깅 형식 소개(Michael J. Eager).
디버깅 정보가 필요한 이유는 무엇입니까? 이는 다음을 허용합니다:
  • 실제 주소가 아닌 소스 코드 파일의 줄 번호나 함수 이름에 중단점을 설정합니다.
  • 함수 매개변수뿐만 아니라 전역 및 지역 변수의 값을 표시하고 변경합니다.
  • 호출 스택 표시(역추적)
  • 하나의 어셈블리 명령이 아닌 소스 코드 라인에 따라 단계별로 프로그램을 실행합니다.
이 정보는 트리 구조로 저장됩니다. 각 트리 노드에는 부모가 있고 자식도 있을 수 있으며 이를 DIE(디버깅 정보 항목)라고 합니다. 각 노드에는 고유한 태그(유형)와 노드를 설명하는 속성(속성) 목록이 있습니다. 속성에는 데이터나 다른 노드에 대한 링크 등 무엇이든 포함될 수 있습니다. 또한 트리 외부에 정보가 저장되어 있습니다.
노드는 데이터를 설명하는 노드와 코드를 설명하는 노드라는 두 가지 주요 유형으로 나뉩니다.
데이터를 설명하는 노드:
  1. 데이터 유형:
    • C의 int 유형과 같은 기본 데이터 유형(DW_TAG_base_type 유형의 노드)
    • 복합 데이터 유형(포인터 등)
    • 배열
    • 구조체, 클래스, 공용체, 인터페이스
  2. 데이터 개체:
    • 상수
    • 함수 매개변수
    • 변수
    • 등.
각 데이터 개체에는 데이터가 있는 주소를 계산하는 방법을 지정하는 DW_AT_location 속성이 있습니다. 예를 들어, 변수는 고정된 주소를 가질 수도 있고, 레지스터나 스택에 있을 수도 있고, 클래스나 객체의 멤버일 수도 있습니다. 이 주소는 다소 복잡한 방식으로 계산될 수 있으므로 표준은 특수 내부 스택 시스템의 일련의 연산자를 포함할 수 있는 소위 위치 표현식을 제공합니다.
코드를 설명하는 노드:
  1. 프로시저(함수) - DW_TAG_subprogram 태그가 있는 노드입니다. 하위 노드에는 변수(함수 매개변수 및 로컬 함수 변수)에 대한 설명이 포함될 수 있습니다.
  2. 컴파일 유닛. 프로그램 정보를 포함하며 다른 모든 노드의 상위 노드입니다.
위에 설명된 정보는 ".debug_info" 및 ".debug_abbrev" 섹션에 있습니다.
기타 정보:
  • 줄 번호에 대한 정보(".debug_line" 섹션)
  • 매크로에 대한 정보(".debug_macinfo" 섹션)
  • 호출 프레임 정보(".debug_frame" 섹션)

ELF의 생성

elfutils 패키지의 libelf 라이브러리를 사용하여 EFL 형식의 파일을 생성합니다. 인터넷에는 libelf 사용에 대한 좋은 기사(예제별 LibELF)(불행히도 파일 생성에 대해 매우 간략하게 설명)와 문서가 있습니다.
파일 생성은 여러 단계로 구성됩니다.
  1. 명예훼손 초기화 중
  2. 파일 헤더 생성(ELF 헤더)
  3. 프로그램 헤더 테이블 생성
  4. 섹션 생성
  5. 파일 쓰기
스테이지를 좀 더 자세히 살펴보자
명예훼손 초기화 중
먼저 elf_version(EV_CURRENT) 함수를 호출하고 결과를 확인해야 합니다. EV_NONE과 같으면 오류가 발생한 것이므로 추가 작업을 수행할 수 없습니다. 그런 다음 디스크에 필요한 파일을 생성하고 해당 설명자를 가져와 elf_begin 함수에 전달해야 합니다.
Elf * elf_begin(int fd, Elf_Cmd cmd, Elf *elf)
  • fd - 새로 열린 파일의 설명자
  • cmd - 모드(정보 읽기용 ELF_C_READ, 쓰기용 ELF_C_WRITE 또는 읽기/쓰기용 ELF_C_RDWR), 열린 파일 모드(우리의 경우 ELF_C_WRITE)와 일치해야 합니다.
  • elf - 아카이브 파일(.a) 작업에만 필요합니다. 이 경우에는 0을 전달해야 합니다.
이 함수는 모든 libelf 함수에서 사용되는 생성된 핸들에 대한 포인터를 반환하며 오류 시 0이 반환됩니다.
제목 만들기
elf32_newehdr 함수에 의해 새 파일 헤더가 생성됩니다.
Elf32_Ehdr * elf32_newehdr(Elf *elf);
  • elf - elf_begin 함수에 의해 반환된 핸들
오류가 발생하면 0을 반환하거나 ELF 파일의 헤더인 구조에 대한 포인터를 반환합니다.
#define EI_NIDENT 16 typedef struct ( unsigned char e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehs ize; 하프 e_phentsize; Elf32_Half e_shntsize ) Elf32_Ehdr;

해당 필드 중 일부는 표준 방식으로 채워지고 일부는 채워야 합니다.

  • e_ident - 식별 바이트 배열이며 다음 인덱스를 갖습니다.
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 - 이 4바이트에는 elf32_newehdr 함수가 이미 수행한 문자 0x7f,"ELF"가 포함되어야 합니다.
    • EI_DATA - 파일의 데이터 인코딩 유형(ELFDATA2LSB 또는 ELFDATA2MSB)을 나타냅니다. 다음과 같이 ELFDATA2LSB를 설치해야 합니다: e_ident = ELFDATA2LSB
    • EI_VERSION - 파일 헤더 버전, 이미 설정되어 있음
    • EI_PAD - 만지지 마세요
  • e_type - 파일 유형, ET_NONE - 유형 없음, ET_REL - 재배치 가능한 파일, ET_EXEC - 실행 파일, ET_DYN - 공유 객체 파일 등이 될 수 있습니다. 파일 형식을 ET_EXEC로 설정해야 합니다.
  • e_machine - 이 파일에 필요한 아키텍처(예: EM_386) - Intel 아키텍처의 경우, ARM의 경우 여기에 EM_ARM(40)을 작성해야 합니다. - ARM 아키텍처는 ELF를 참조하세요.
  • e_version - 파일 버전은 EV_CURRENT로 설정되어야 합니다.
  • e_entry - 진입점 주소, 우리에게는 필요하지 않음
  • e_phoff - 프로그램 헤더 파일의 오프셋, e_shoff - 섹션 헤더의 오프셋, 채우지 않음
  • e_flags - 아키텍처(Cortex-M3)의 프로세서별 플래그는 0x05000000(ABI 버전 5)으로 설정되어야 합니다.
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - 만지지 마세요
  • e_shstrndx - 섹션 헤더가 있는 행 테이블이 있는 섹션 번호를 포함합니다. 아직 섹션이 없으므로 이 숫자는 나중에 설정하겠습니다.
프로그램 헤더 생성
이미 언급했듯이 프로그램 헤더 테이블은 파일 섹션과 메모리 세그먼트 간의 대응 테이블로, 부트로더에게 각 섹션을 쓸 위치를 알려줍니다. 제목은 elf32_newphdr 함수를 사용하여 생성됩니다.
Elf32_Phdr * elf32_newphdr(Elf *elf, size_t count);
  • 엘프는 우리의 손잡이야
  • count - 생성되는 테이블 요소의 수. 프로그램 코드가 포함된 섹션이 하나만 있으므로 개수는 1이 됩니다.
오류가 발생하면 0을 반환하거나 프로그램 제목에 대한 포인터를 반환합니다.
헤더 테이블의 각 요소는 다음 구조로 설명됩니다.
TYPEDEF STRUCT(ELF32_WORD P_TYPE; ELF32_OFF P_OFFSET; ELF32_ADDR P_VADDR; ELF32_ADDR P_PADDDR; ELF32_WORD P_Filesz; ELF32_WORD P_MEMSZZ; ELF32_WORD P_FL AGS;
  • p_type - 세그먼트(섹션) 유형, 여기서는 지정해야 합니다. PT_LOAD - 세그먼트 로드
  • p_offset - 메모리에 로드될 섹션의 데이터가 시작되는 파일의 오프셋입니다. 우리에게 이것은 파일 헤더와 프로그램 헤더 바로 뒤에 위치하는 .text 섹션입니다. 이 헤더 길이의 합으로 오프셋을 계산할 수 있습니다. 모든 유형의 길이는 elf32_fsize 함수를 사용하여 얻을 수 있습니다.
    size_t elf32_fsize(Elf_Type 유형, size_t 개수, unsigned int 버전); 유형 - 여기에는 상수 ELF_T_xxx가 있으며, ELF_T_EHDR 및 ELF_T_PHDR 크기가 필요합니다. count - 원하는 유형의 요소 수, 버전 - EV_CURRENT로 설정되어야 함
  • p_vaddr, p_paddr - 섹션의 내용이 로드될 가상 및 물리적 주소입니다. 가상 주소가 없기 때문에 이를 실제 주소와 동일하게 설정합니다(가장 간단한 경우 0). 왜냐하면 이곳이 프로그램이 로드되는 곳이기 때문입니다.
  • p_filesz, p_memsz - 파일 및 메모리의 섹션 크기입니다. 동일하지만 아직 프로그램 코드가 포함된 섹션이 없으므로 나중에 설치하겠습니다.
  • p_flags - 로드된 메모리 세그먼트에 대한 권한입니다. PF_R - 읽기, PF_W - 쓰기, PF_X - 실행 또는 이들의 조합일 수 있습니다. p_flags를 PF_R + PF_X로 설정
  • p_align - 세그먼트 정렬, 4개가 있습니다.
섹션 생성
제목을 만든 후 섹션 만들기를 시작할 수 있습니다. elf_newscn 함수를 사용하여 빈 섹션을 만듭니다:
Elf_Scn * elf_newscn(Elf *elf);
  • elf - elf_begin 함수에 의해 이전에 반환된 핸들
이 함수는 섹션에 대한 포인터를 반환하거나 오류가 발생하면 0을 반환합니다.
섹션을 생성한 후에는 섹션 헤더를 작성하고 섹션 데이터 설명자를 생성해야 합니다.
elf32_getshdr 함수를 사용하여 섹션 헤더에 대한 포인터를 얻을 수 있습니다:
Elf32_Shdr * elf32_getshdr(Elf_Scn *scn);
  • scn은 elf_newscn 함수에서 받은 섹션에 대한 포인터입니다.
섹션 헤더는 다음과 같습니다.
typedef 구조체 ( Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; 32_Word sh_entsize; ) 32_Shdr;
  • sh_name - 섹션 이름 - 섹션 헤더의 문자열 테이블(section.shstrtab)의 오프셋 - 아래 "문자열 테이블" 참조
  • sh_type - 섹션 콘텐츠 유형, 프로그램 코드가 있는 섹션의 경우 SHT_PROGBITS를 설정해야 함, 문자열 테이블이 있는 섹션의 경우 - SHT_STRTAB, 기호 테이블의 경우 - SHT_SYMTAB
  • sh_flags는 결합할 수 있는 섹션 플래그이며, 그 중 3개만 필요합니다:
    • SHF_ALLOC - 섹션이 메모리에 로드됨을 의미합니다.
    • SHF_EXECINSTR - 실행 가능한 코드가 포함된 섹션
    • SHF_STRINGS - 섹션에 문자열 테이블이 포함되어 있습니다.
    따라서 프로그램이 포함된 .text 섹션의 경우 SHF_ALLOC + SHF_EXECINSTR 플래그를 설정해야 합니다.
  • sh_addr - 섹션이 메모리에 로드될 주소
  • sh_offset - 파일 섹션의 오프셋 - 건드리지 마세요. 라이브러리가 자동으로 설치해 드립니다.
  • sh_size - 섹션 크기 - 만지지 마세요.
  • sh_link - 섹션을 해당 행 테이블과 연결하는 데 필요한 링크된 섹션의 번호를 포함합니다(아래 참조).
  • sh_info - 섹션 유형에 따른 추가 정보, 0으로 설정
  • sh_addralign - 주소 정렬, 건드리지 마세요.
  • sh_entsize - 섹션이 동일한 길이의 여러 요소로 구성된 경우 해당 요소의 길이를 나타내며 만지지 마십시오.
헤더를 작성한 후 elf_newdata 함수를 사용하여 섹션 데이터 설명자를 생성해야 합니다.
Elf_Data * elf_newdata(Elf_Scn *scn);
  • scn은 새 섹션에 대해 새로 수신된 포인터입니다.
이 함수는 오류 시 0을 반환하거나 채워야 하는 Elf_Data 구조에 대한 포인터를 반환합니다.
typedef 구조체 ( void* d_buf; Elf_Type d_type; size_t d_size; off_t d_off; size_t d_align; unsigned d_version; ) Elf_Data;
  • d_buf - 섹션에 기록될 데이터에 대한 포인터
  • d_type - 데이터 유형, ELF_T_BYTE는 어디에서나 적합합니다.
  • d_size - 데이터 크기
  • d_off - 섹션의 오프셋, 0으로 설정
  • d_align - 정렬, 1로 설정 가능 - 정렬 없음
  • d_version - 버전, EV_CURRENT로 설정되어야 함
특별 섹션
우리의 목적을 위해서는 최소한으로 필요한 섹션 세트를 만들어야 합니다.
  • .text - 프로그램 코드가 포함된 섹션
  • .symtab - 파일 기호 테이블
  • .strtab은 .symtab 섹션의 기호 이름을 포함하는 문자열 테이블입니다. 왜냐하면 후자는 이름 자체가 아니라 해당 인덱스를 저장하기 때문입니다.
  • .shstrtab - 섹션 이름이 포함된 문자열 테이블
모든 섹션은 이전 섹션에서 설명한 대로 생성되지만 각 특수 섹션에는 고유한 특성이 있습니다.
섹션.텍스트
이 섹션에는 실행 가능한 코드가 포함되어 있으므로 sh_type을 SHT_PROGBITS로, sh_flags를 SHF_EXECINSTR + SHF_ALLOC로, sh_addr을 이 코드가 로드될 주소로 설정해야 합니다.
섹션.symtab
이 섹션에는 프로그램의 모든 기호(기능)에 대한 설명과 해당 기호(기능)가 설명된 파일이 포함되어 있습니다. 이는 각각 16바이트 길이의 다음 요소로 구성됩니다.
typedef struct ( Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; ) Elf32_Sym;
  • st_name - 기호 이름(문자열 table.strtab의 인덱스)
  • st_value - 값(함수의 경우 항목 주소, 파일의 경우 0). Cortex-M3에는 Thumb-2 명령어 세트가 있으므로 이 주소는 홀수여야 합니다(실제 주소 + 1).
  • st_size - 함수 코드 길이(파일의 경우 0)
  • st_info - 기호 유형 및 해당 범위. 이 필드의 값을 결정하는 매크로가 있습니다.
    #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
    여기서 b는 범위이고 t는 문자 유형입니다.
    범위는 STB_LOCAL(다른 개체 파일에서는 표시되지 않는 기호) 또는 STB_GLOBAL(표시)일 수 있습니다. 단순화하기 위해 STB_GLOBAL을 사용합니다.
    기호 유형 - 함수의 경우 STT_FUNC, 파일의 경우 STT_FILE
  • st_other - 0으로 설정
  • st_shndx - 기호가 정의된 섹션의 인덱스(섹션 index.text) 또는 파일의 경우 SHN_ABS입니다.
    scn 설명자의 섹션 인덱스는 elf_ndxscn을 사용하여 확인할 수 있습니다.
    size_t elf_ndxscn(Elf_Scn *scn);

이 섹션은 일반적인 방법으로 생성되며 sh_type만 SHT_SYMTAB로 설정하고 index.strtab 섹션을 sh_link 필드에 작성해야 이 섹션이 연결됩니다.
섹션.strtab
이 섹션에는 .symtab 섹션의 모든 기호 이름이 포함되어 있습니다. 일반 섹션처럼 생성되지만 sh_type을 SHT_STRTAB로, sh_flags를 SHF_STRINGS로 설정해야 이 섹션이 스트링 테이블이 됩니다.
섹션에 대한 데이터는 소스 텍스트를 배열로 전달하여 수집할 수 있으며, 해당 포인터는 섹션 데이터 설명자(d_buf)에 기록됩니다.
섹션.shstrtab
섹션은 자체 헤더를 포함하여 파일의 모든 섹션 헤더를 포함하는 문자열 테이블입니다. .strtab 섹션과 동일한 방식으로 생성됩니다. 생성 후 해당 인덱스는 파일 헤더의 e_shstrndx 필드에 기록되어야 합니다.
스트링 테이블
문자열 테이블에는 널 바이트로 끝나는 연속 행이 포함되어 있으며, 이 테이블의 첫 번째 바이트도 0이어야 합니다. 테이블의 행 인덱스는 단순히 테이블 시작 부분으로부터의 오프셋(바이트 단위)이므로 첫 번째 행 "name" 인덱스 1이 있고 다음 행 "var"에는 인덱스 6이 있습니다.
인덱스 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \0 n a m e \0 v a r \0
파일 쓰기
따라서 헤더와 섹션이 이미 구성되었으므로 이제 파일에 작성하고 libelf 작업을 완료해야 합니다. 녹음은 elf_update 함수에 의해 수행됩니다:
off_t elf_update(Elf *elf, Elf_Cmd cmd);
  • 엘프 - 손잡이
  • cmd - 명령을 쓰려면 ELF_C_WRITE와 동일해야 합니다.
이 함수는 오류 시 -1을 반환합니다. 오류 텍스트는 오류가 있는 줄에 대한 포인터를 반환하는 elf_errmsg(-1) 함수를 호출하여 얻을 수 있습니다.
설명자를 전달하는 elf_end 함수를 사용하여 라이브러리 작업을 마칩니다. 남은 것은 이전에 열었던 파일을 닫는 것뿐입니다.
그러나 생성된 파일에는 다음 섹션에 추가할 디버깅 정보가 포함되어 있지 않습니다.

드워프의 창조

문서가 포함된 PDF 파일(libdwarf2p.1.pdf - DWARF에 대한 생산자 라이브러리 인터페이스)과 함께 제공되는 라이브러리를 사용하여 디버깅 정보를 생성합니다.
디버깅 정보 생성은 다음 단계로 구성됩니다.
  1. 노드 생성(DIE - 디버깅 정보 항목)
  2. 노드 속성 생성
  3. 데이터 유형 생성
  4. 프로시저(함수) 생성
스테이지를 좀 더 자세히 살펴보자
libdwarf 생산자 초기화 중
.symtab 섹션에서 심볼을 생성하는 동시에 컴파일 타임에 디버깅 정보를 생성할 것이므로 libelf가 초기화된 후, ELF 헤더 및 프로그램 헤더가 생성된 후, 섹션이 생성되기 전에 라이브러리를 초기화해야 합니다.
초기화를 위해 dwarf_producer_init_c 함수를 사용합니다. 라이브러리에는 문서에 설명된 일부 뉘앙스가 다른 몇 가지 초기화 함수(dwarf_producer_init, dwarf_producer_init_b)가 더 있습니다. 원칙적으로 둘 중 하나를 사용할 수 있습니다.

Dwarf_P_Debug dwarf_producer_init_c(Dwarf_Unsigned 플래그, Dwarf_Callback_Func_c func, Dwarf_Handler 오류, Dwarf_Ptr 오류, void * user_data, Dwarf_Error *error)

  • 플래그 - 정보 비트 깊이, 바이트 시퀀스(리틀 엔디안, 빅 엔디안), 재배치 형식과 같은 일부 매개변수를 결정하는 여러 상수의 "또는" 조합으로, DW_DLC_WRITE 및 DW_DLC_SYMBOLIC_RELOCATIONS가 반드시 필요합니다.
  • func는 디버깅 정보가 포함된 ELF 섹션을 생성할 때 호출되는 콜백 함수입니다. 자세한 내용은 아래 '디버깅 정보가 포함된 섹션 생성' 섹션을 참조하세요.
  • errhand는 오류가 발생할 때 호출될 함수에 대한 포인터입니다. 0을 보낼 수 있습니다.
  • errarg - errhand 함수에 전달될 데이터를 0으로 설정할 수 있습니다.
  • user_data - func 함수에 전달될 데이터를 0으로 설정할 수 있습니다.
  • 오류 - 반환된 오류 코드
함수는 Dwarf_P_Debug를 반환합니다. 이는 모든 후속 함수에 사용되는 설명자이거나 오류가 발생한 경우 -1이며 오류에는 오류 코드가 포함됩니다. 이 코드를 전달하는 dwarf_errmsg 함수를 사용하여 해당 코드로 오류 메시지 텍스트를 얻을 수 있습니다 그것에)
노드 생성(DIE - 디버깅 정보 입력)
전술한 바와 같이 디버깅 정보는 트리 구조를 이룬다. 이 트리의 노드를 생성하려면 다음이 필요합니다.
  • dwarf_new_die 함수로 생성하세요
  • 속성을 추가합니다(각 속성 유형은 자체 기능에 의해 추가되며 이에 대해서는 아래에서 설명합니다).
노드는 dwarf_new_die 함수를 사용하여 생성됩니다.
Dwarf_P_Die dwarf_new_die(Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die 부모, Dwarf_P_Die 자식, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf_Error *error)
  • new_tag - 노드 태그(유형) - libdwarf.h 파일에서 찾을 수 있는 상수 DW_TAG_xxxx
  • parent, child, left_sibling, right_sibling - 각각 노드의 부모, 자식, 왼쪽 및 오른쪽 이웃입니다. 이러한 매개변수를 모두 지정할 필요는 없습니다. 하나만 지정하고 나머지 매개변수 대신 0을 입력하면 노드가 루트 또는 격리됩니다.
  • 오류 - 발생 시 오류 코드가 포함됩니다.
이 함수는 오류 시 DW_DLV_BADADDR을 반환하거나 성공 시 Dwarf_P_Die 노드 핸들을 반환합니다.
노드 속성 생성
노드 속성을 생성하기 위해 dwarf_add_AT_xxxx 함수 전체가 있습니다. 때로는 필수 속성을 생성하기 위해 어떤 함수가 필요한지 판단하기 어려울 때도 있어 라이브러리의 소스 코드를 여러 번 파헤치기도 했습니다. 일부 기능은 여기에서 설명하고 일부는 아래 해당 섹션에서 설명합니다. 이들은 모두 속성이 추가될 노드에 대한 핸들인 ownerdie 매개변수를 허용하고 오류 매개변수에 오류 코드를 반환합니다.
dwarf_add_AT_name 함수는 노드에 "name" 속성(DW_AT_name)을 추가합니다. 대부분의 노드에는 이름(예: 프로시저, 변수, 상수)이 있어야 하며 일부 노드에는 이름이 없을 수도 있습니다(예: 컴파일 단위)
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die 소유자, char *name, Dwarf_Error *error)
  • name - 실제 속성 값(노드 이름)

dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const 함수는 지정된 속성과 해당 속성의 부호 있는(부호 없는) 값을 노드에 추가합니다. 부호 있는 속성과 부호 없는 속성은 상수 값, 크기, 줄 번호 등을 지정하는 데 사용됩니다. 기능 형식:
Dwarf_P_Attribute dwarf_add_AT_(un)signed_const(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half 속성, Dwarf_Signed 값, Dwarf_Error *error)
  • dbg - 라이브러리 초기화 중에 얻은 Dwarf_P_Debug 설명자
  • attr - 값이 설정된 속성 - libdwarf.h 파일에서 찾을 수 있는 DW_AT_xxxx 상수
  • 값 - 속성 값
오류가 발생하면 DW_DLV_BADADDR을 반환하고, 성공하면 속성 핸들을 반환합니다.
편집 단위 생성
모든 트리에는 루트가 있어야 합니다. 우리의 경우 이는 프로그램에 대한 정보(예: 기본 파일 이름, 사용된 프로그래밍 언어, 컴파일러 이름, 기호의 대/소문자 구분 등)가 포함된 컴파일 단위입니다. 변수, 함수), 프로그램의 주요 기능, 시작 주소 등). 원칙적으로 속성은 필요하지 않습니다. 예를 들어, 메인 파일과 컴파일러에 대한 정보를 생성해 보겠습니다.
주요 파일 정보
기본 파일에 대한 정보를 저장하려면 "노드 속성 생성" 섹션에 표시된 대로 name 속성(DW_AT_name)을 사용하고 dwarf_add_AT_name 함수를 사용합니다.
컴파일러 정보
dwarf_add_AT_producer 함수를 사용합니다:
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die 소유자, char *producer_string, Dwarf_Error *error)
  • producer_string - 정보 텍스트가 포함된 문자열
오류가 발생하면 DW_DLV_BADADDR을 반환하고, 성공하면 속성 핸들을 반환합니다.
공통 정보 항목 생성
일반적으로 함수(서브루틴)가 호출되면 해당 매개변수와 반환 주소가 스택에 푸시됩니다(각 컴파일러는 이를 다르게 수행할 수 있음). 이 모든 것을 호출 프레임이라고 합니다. 디버거는 함수의 반환 주소를 올바르게 결정하고 역추적(현재 함수로 연결되는 함수 호출 체인 및 이러한 함수의 매개 변수)을 구축하기 위해 프레임 형식에 대한 정보가 필요합니다. 스택에 저장되는 프로세서 레지스터를 지정하는 것도 일반적입니다. 스택에 공간을 예약하고 프로세서 레지스터를 저장하는 코드를 함수 프롤로그라고 하며, 레지스터와 스택을 복원하는 코드를 에필로그라고 합니다.
이 정보는 컴파일러에 따라 크게 달라집니다. 예를 들어, 프롤로그와 에필로그가 기능의 시작과 끝 부분에 있을 필요는 없습니다. 프레임이 사용되는 경우도 있고 사용하지 않는 경우도 있습니다. 프로세서 레지스터는 다른 레지스터 등에 저장될 수 있습니다.
따라서 디버거는 프로세서 레지스터가 값을 변경하는 방법과 프로시저에 들어갈 때 저장되는 위치를 알아야 합니다. 이 정보를 호출 프레임 정보(프레임 형식에 대한 정보)라고 합니다. 프로그램의 각 주소(코드 포함)에 대해 메모리의 프레임 주소(표준 프레임 주소 - CFA) 및 프로세서 레지스터에 대한 정보가 표시됩니다. 예를 들어 다음을 나타낼 수 있습니다.
  • 사건은 절차에서 보존되지 않습니다
  • 레지스터는 프로시저에서 해당 값을 변경하지 않습니다.
  • 레지스터는 스택의 주소 CFA+n에 저장됩니다.
  • 레지스터가 다른 레지스터에 저장되어 있음
  • 레지스터는 메모리의 일부 주소에 저장되며 이는 다소 명확하지 않은 방식으로 계산될 수 있습니다.
  • 등.
코드의 각 주소에 대해 정보를 지정해야 하기 때문에 매우 방대하며 .debug_frame 섹션에 압축된 형태로 저장됩니다. 주소마다 거의 변경되지 않으므로 해당 변경 사항만 DW_CFA_xxxx 명령어 형식으로 인코딩됩니다. 각 명령은 하나의 변경 사항을 나타냅니다. 예를 들면 다음과 같습니다.
  • DW_CFA_set_loc - 프로그램의 현재 주소를 가리킵니다.
  • DW_CFA_advance_loc - 특정 바이트 수만큼 포인터를 전진시킵니다.
  • DW_CFA_def_cfa - 스택 프레임의 주소를 나타냅니다(숫자 상수).
  • DW_CFA_def_cfa_register - 스택 프레임의 주소를 나타냅니다(프로세서 레지스터에서 가져옴).
  • DW_CFA_def_cfa_expression - 스택 프레임 주소를 계산하는 방법을 지정합니다.
  • DW_CFA_same_value - 레지스터가 변경되지 않았음을 나타냅니다.
  • DW_CFA_register - 레지스터가 다른 레지스터에 저장되어 있음을 나타냅니다.
  • 등.
.debug_frame 섹션의 요소는 CIE(공통 정보 항목) 및 FDE(프레임 설명 항목)의 두 가지 유형일 수 있는 항목입니다. CIE에는 많은 FDE 레코드에 공통적인 정보가 포함되어 있으며 대략적으로 말하면 특정 유형의 절차를 설명합니다. FDE는 각 특정 절차를 설명합니다. 프로시저에 들어갈 때 디버거는 먼저 CIE의 명령을 실행한 다음 FDE의 명령을 실행합니다.
내 컴파일러는 CFA가 sp 레지스터(r13)에 있는 프로시저를 생성합니다. 모든 절차에 대한 CIE를 만들어 보겠습니다. 이를 위한 함수인 dwarf_add_frame_cie가 있습니다:
Dwarf_Unsigned dwarf_add_frame_cie(Dwarf_P_Debug dbg, char *augmenter, Dwarf_Small code_align, Dwarf_Small data_align, Dwarf_Small ret_addr_reg, Dwarf_Ptr init_bytes, Dwarf_Unsigned init_bytes_len, Dwarf_Error *error);
  • Augmenter는 UTF-8로 인코딩된 문자열이며, 이 문자열이 있으면 CIE 또는 FDE에 대한 추가 플랫폼 종속 정보가 있음을 나타냅니다. 빈 줄을 넣으세요
  • code_align - 바이트 단위의 코드 정렬(2개 있음)
  • data_align - 프레임의 데이터 정렬(설정 -4, 이는 모든 매개변수가 스택에서 4바이트를 차지하고 메모리에서 작아진다는 의미)
  • ret_addr_reg - 프로시저의 반환 주소가 포함된 레지스터(14개)
  • init_bytes - DW_CFA_xxxx 명령이 포함된 배열입니다. 불행하게도 이 배열을 생성하는 편리한 방법은 없습니다. 수동으로 생성할 수도 있고 C 컴파일러에서 생성한 elf 파일에서 볼 수도 있습니다. 저는 그렇게 했습니다. 내 경우에는 3바이트(0x0C, 0x0D, 0)가 포함되어 있으며 이는 DW_CFA_def_cfa: r13 ofs 0(CFA는 레지스터 r13에 있고 오프셋은 0)을 나타냅니다.
  • init_bytes_len - init_bytes 배열의 길이
이 함수는 오류 발생 시 DW_DLV_NOCOUNT를 반환하거나 각 프로시저에 대해 FDE를 생성할 때 사용해야 하는 CIE 핸들을 반환합니다. 이에 대해서는 나중에 "FDE 프로시저 생성" 섹션에서 살펴보겠습니다.
데이터 유형 생성
프로시저와 변수를 생성하려면 먼저 데이터 유형에 해당하는 노드를 생성해야 합니다. 많은 데이터 유형이 있지만 모두 기본 유형(int, double 등과 같은 기본 유형)을 기반으로 하며, 다른 유형은 기본 유형을 기반으로 구축됩니다.
기본 유형은 DW_TAG_base_type 태그가 있는 노드입니다. 다음 속성이 있어야 합니다.
  • "이름"(DW_AT_name)
  • "인코딩"(DW_AT_encoding) - 이 기본 유형을 설명하는 데이터 종류를 의미합니다(예: DW_ATE_boolean - 논리, DW_ATE_float - 부동 소수점, DW_ATE_signed - 부호 있는 정수, DW_ATE_unsigned - 부호 없는 정수 등).
  • "크기"(DW_AT_byte_size - 바이트 단위 크기 또는 DW_AT_bit_size - 비트 단위 크기)
노드에는 다른 선택적 속성이 포함될 수도 있습니다.
예를 들어, 32비트 부호 있는 정수 기본 유형 "int"를 생성하려면 DW_TAG_base_type 태그가 있는 노드를 생성하고 해당 속성을 DW_AT_name - "int", DW_AT_encoding - DW_ATE_signed, DW_AT_byte_size - 4로 설정해야 합니다.
기본 유형이 생성되면 해당 유형에서 파생 항목을 파생할 수 있습니다. 이러한 노드에는 기본 유형에 대한 참조인 DW_AT_type 속성이 포함되어야 합니다. 예를 들어, int에 대한 포인터 - DW_TAG_pointer_type 태그가 있는 노드는 DW_AT_type 속성에 이전에 생성된 "int" 유형에 대한 링크를 포함해야 합니다.
다른 노드에 대한 참조가 있는 속성은 dwarf_add_AT_reference 함수에 의해 생성됩니다.
Dwarf_P_Attribute dwarf_add_AT_reference(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Die otherdie, Dwarf_Error *error)
  • attr - 속성(이 경우 DW_AT_type)
  • otherdie - 참조되는 유형의 노드에 대한 핸들
프로시저 생성
프로시저를 작성하려면 디버깅 정보의 한 가지 유형인 행 번호 정보를 더 설명해야 합니다. 이는 각 기계 명령어를 특정 소스 코드 줄에 매핑하고 프로그램의 한 줄씩 디버깅을 허용하는 역할을 합니다. 이 정보는 .debug_line 섹션에 저장됩니다. 공간이 충분하다면 각 명령어마다 한 행씩 다음과 같은 열이 있는 행렬로 저장됩니다.
  • 소스 파일 이름
  • 이 파일의 줄 번호
  • 파일의 열 번호
  • 명령어가 명령문의 시작인지 아니면 명령문 블록인지 여부
  • 등.
이러한 행렬은 매우 크기 때문에 압축해야 합니다. 첫째, 중복된 줄이 삭제되고, 둘째, 줄 자체가 저장되지 않고 해당 줄의 변경 사항만 저장됩니다. 이러한 변경 사항은 유한 상태 기계에 대한 명령처럼 보이며 정보 자체는 이미 이 기계에 의해 "실행"될 프로그램으로 간주됩니다. 예를 들어 이 프로그램의 명령은 다음과 같습니다. DW_LNS_advance_pc - 프로그램 카운터를 특정 주소로 전진, DW_LNS_set_file - 프로시저가 정의된 파일 설정, DW_LNS_const_add_pc - 프로그램 카운터를 몇 바이트씩 전진시키는 등.
이렇게 낮은 수준에서 이 정보를 생성하는 것은 어렵기 때문에 libdwarf는 이 작업을 더 쉽게 하기 위해 여러 기능을 제공합니다.
각 명령어의 파일 이름을 저장하는 것은 비용이 많이 들기 때문에 이름 대신 해당 인덱스가 특수 테이블에 저장됩니다. 파일 인덱스를 생성하려면 dwarf_add_file_decl 함수를 사용해야 합니다:
Dwarf_Unsigned dwarf_add_file_decl(Dwarf_P_Debug dbg, char *name, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned 길이, Dwarf_Error *error)
  • 이름 - 파일 이름
  • dir_idx - 파일이 있는 폴더의 인덱스입니다. dwarf_add_directory_decl 함수를 사용하여 인덱스를 얻을 수 있습니다. 전체 경로를 사용하는 경우 폴더 인덱스를 0으로 설정하고 dwarf_add_directory_decl을 전혀 사용하지 않을 수 있습니다.
  • time_mod - 파일 수정 시간, 지정할 수 없습니다(0).
  • 길이 - 파일 크기(선택 사항) (0)
이 함수는 오류 발생 시 파일 인덱스 또는 DW_DLV_NOCOUNT를 반환합니다.
줄 번호에 대한 정보를 생성하기 위해 dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence 세 가지 함수가 있는데, 아래에서 살펴보겠습니다.
프로시저에 대한 디버깅 정보 생성은 여러 단계로 이루어집니다.
  • .symtab 섹션에 프로시저 기호 생성
  • 속성이 있는 프로시저 노드 생성
  • FDE 프로시저 생성
  • 프로시저 매개변수 생성
  • 라인 번호 정보 생성
절차 기호 만들기
프로시저 기호는 위의 Section.symtab 섹션에 설명된 대로 생성됩니다. 여기에는 절차 기호가 해당 절차의 소스 코드가 있는 파일 기호와 함께 배치됩니다. 먼저 파일 기호를 만든 다음 프로시저를 만듭니다. 이렇게 하면 파일이 현재 파일이 되고 다음 절차가 현재 파일에 있는 경우 파일 기호를 다시 만들 필요가 없습니다.
속성을 사용하여 프로시저 노드 생성
먼저 dwarf_new_die 함수("노드 생성" 섹션 참조)를 사용하여 노드를 생성하고 DW_TAG_subprogram을 태그로 지정하고 컴파일 유닛(전역 프로시저인 경우) 또는 해당 DIE(로컬인 경우)를 상위로 지정합니다. 다음으로 속성을 생성합니다.
  • 프로시저 이름(dwarf_add_AT_name 함수, "노드 속성 생성" 참조)
  • 프로시저 코드가 시작되는 파일의 줄 번호(DW_AT_decl_line 속성), dwarf_add_AT_unsigned_const 함수(“노드 속성 생성” 참조)
  • 프로시저의 시작 주소(DW_AT_low_pc 속성), dwarf_add_AT_targ_address 함수, 아래 참조
  • 프로시저의 최종 주소(속성 DW_AT_high_pc), dwarf_add_AT_targ_address 함수, 아래 참조
  • 프로시저에서 반환된 결과 유형(DW_AT_type 속성은 이전에 생성된 유형에 대한 링크입니다. "데이터 유형 생성" 참조) 프로시저가 아무것도 반환하지 않으면 이 속성을 생성할 필요가 없습니다.
DW_AT_low_pc 및 DW_AT_high_pc 속성은 이 목적을 위해 특별히 설계된 dwarf_add_AT_targ_address_b 함수를 사용하여 생성되어야 합니다.
Dwarf_P_Attribute dwarf_add_AT_targ_address_b(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Unsigned pc_value, Dwarf_Unsigned Sym_index, Dwarf_Error *error)
  • attr - 속성(DW_AT_low_pc 또는 DW_AT_high_pc)
  • pc_value - 주소 값
  • Sym_index - table.symtab에 있는 프로시저 기호의 인덱스입니다. 선택 사항, 0을 전달할 수 있습니다.
이 함수는 오류가 발생하면 DW_DLV_BADADDR을 반환합니다.
FDE 프로시저 생성
위의 "공통 정보 항목 생성" 섹션에서 설명한 것처럼 각 절차에 대해 여러 단계로 진행되는 프레임 설명자를 생성해야 합니다.
  • 새 FDE 생성(공통 정보 항목 생성 참조)
  • 생성된 FDE를 일반 목록에 추가
  • 생성된 FDE에 지침 추가
dwarf_new_fde 함수를 사용하여 새 FDE를 만들 수 있습니다.
Dwarf_P_Fde dwarf_new_fde(Dwarf_P_Debug dbg, Dwarf_Error *error)
이 함수는 오류 발생 시 새 FDE 또는 DW_DLV_BADADDR에 대한 핸들을 반환합니다.
dwarf_add_frame_fde를 사용하여 목록에 새 FDE를 연결할 수 있습니다.
Dwarf_Unsigned dwarf_add_frame_fde(Dwarf_P_Debug dbg, Dwarf_P_Fde fde, Dwarf_P_Die 다이, Dwarf_Unsigned cie, Dwarf_Addr virt_addr, Dwarf_Unsigned code_len, Dwarf_Unsigned Sym_idx, Dwarf_Error* 오류)
  • fde - 방금 받은 핸들
  • die - DIE 프로시저(속성이 있는 프로시저 노드 생성 참조)
  • cie - CIE 설명자(공통 정보 항목 생성 참조)
  • virt_addr - 프로시저의 시작 주소
  • code_len - 프로시저 길이(바이트)
이 함수는 오류가 발생하면 DW_DLV_NOCOUNT를 반환합니다.
이 모든 과정이 끝나면 DW_CFA_xxxx 지침을 FDE에 추가할 수 있습니다. 이는 dwarf_add_fde_inst 및 dwarf_fde_cfa_offset 함수에 의해 수행됩니다. 첫 번째는 주어진 명령어를 목록에 추가합니다.
Dwarf_P_Fde dwarf_add_fde_inst(Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *error)
  • op - 명령 코드(DW_CFA_хххх)
  • val1, val2 - 명령 매개변수(명령어마다 다름, 표준, 섹션 6.4.2 호출 프레임 명령 참조)
dwarf_fde_cfa_offset 함수는 DW_CFA_offset 명령을 추가합니다.
Dwarf_P_Fde dwarf_fde_cfa_offset(Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed 오프셋, Dwarf_Error *error)
  • fde - 생성된 FDE에 대한 핸들
  • reg - 프레임에 기록되는 레지스터
  • offset - 프레임 내 오프셋(바이트 단위가 아니라 프레임 요소 단위, 공통 정보 항목 생성, data_align 참조)
예를 들어, 컴파일러는 프롤로그가 스택 프레임에 레지스터 lr(r14)을 저장하는 프로시저를 생성합니다. 첫 번째 단계는 첫 번째 매개변수가 1인 DW_CFA_advance_loc 명령어를 추가하는 것입니다. 이는 pc 레지스터를 2바이트씩 전진시키는 것을 의미합니다(공통 정보 항목 생성, code_align 참조). 그런 다음 매개변수 4를 사용하여 DW_CFA_def_cfa_offset을 추가합니다(데이터 오프셋 설정 4바이트씩 프레임) 그리고 매개변수 reg=14 offset=1을 사용하여 dwarf_fde_cfa_offset 함수를 호출합니다. 이는 r14 레지스터를 CFA에서 -4바이트 오프셋을 사용하여 프레임에 쓰는 것을 의미합니다.
프로시저 매개변수 생성
프로시저 매개변수 생성은 일반 변수 생성과 유사합니다. "변수 및 상수 생성"을 참조하세요.
줄 번호에 대한 정보 생성
이 정보는 다음과 같이 생성됩니다.
  • 절차 시작 부분에서 dwarf_lne_set_address 함수를 사용하여 명령어 블록을 시작합니다.
  • 각 코드 줄(또는 기계 명령)에 대해 소스 코드에 대한 정보(dwarf_add_line_entry)를 생성합니다.
  • 절차가 끝나면 dwarf_lne_end_sequence 함수를 사용하여 명령어 블록을 완성합니다.
dwarf_lne_set_address 함수는 명령어 블록이 시작되는 주소를 설정합니다.
Dwarf_Unsigned dwarf_lne_set_address(Dwarf_P_Debug dbg, Dwarf_Addr offs, Dwarf_Unsigned Symidx, Dwarf_Error *error)
  • offs - 프로시저 주소(첫 번째 기계 명령어의 주소)
  • Sym_idx - 기호 인덱스(선택 사항, 0을 지정할 수 있음)

dwarf_add_line_entry_b 함수는 소스 코드 줄에 대한 정보를 .debug_line 섹션에 추가합니다. 나는 모든 기계 명령에 대해 이 함수를 호출합니다.
Dwarf_Unsigned dwarf_add_line_entry_b(Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned lineno, Dwarf_Signed 열_번호, Dwarf_Bool is_source_stmt_begin, Dwarf_Bool is_basic_block_begin, Dwarf_Bool is_epilogue _begin, Dwarf_Bool is_prologue_end, Dwarf_Unsigned isa, Dwarf_Unsigned 판별자, Dwarf_Error *error)
  • file_index - 이전에 dwarf_add_file_decl 함수로 얻은 소스 코드 파일의 인덱스(“프로시저 생성” 참조)
  • code_offset - 현재 기계 명령어의 주소
  • lineno - 소스 코드 파일의 줄 번호
  • column_number - 소스 코드 파일의 열 번호
  • is_source_stmt_begin - 현재 명령어가 lineno 라인의 코드에서 첫 번째인 경우 1(저는 항상 1을 사용합니다)
  • is_basic_block_begin - 현재 명령어가 명령문 블록의 첫 번째 명령어인 경우 1(저는 항상 0을 사용합니다)
  • is_epilogue_begin - 현재 명령이 프로시저 에필로그의 첫 번째 명령인 경우 1(필요하지 않음, 항상 0임)
  • is_prologue_end - 현재 명령어가 프로시저 프롤로그의 마지막 명령어인 경우 1(필수!)
  • isa - 명령어 세트 아키텍처. ARM Cortex M3의 경우 DW_ISA_ARM_thumb을 지정해야 합니다!
  • 판별자. 소스 코드의 한 위치(파일, 줄, 열)는 다른 기계 명령어에 해당할 수 있습니다. 이 경우 해당 명령어 세트에 대해 서로 다른 판별자를 설치해야 합니다. 그러한 경우가 없으면 0이 되어야 한다.
이 함수는 0(성공) 또는 DW_DLV_NOCOUNT(오류)를 반환합니다.
마지막으로 dwarf_lne_end_sequence 함수가 절차를 완료합니다.
Dwarf_Unsigned dwarf_lne_end_sequence(Dwarf_P_Debug dbg, Dwarf_Addr 주소; Dwarf_Error *error)
  • address - 현재 기계 명령의 주소
0(성공) 또는 DW_DLV_NOCOUNT(오류)를 반환합니다.
이것으로 프로시저 생성이 완료됩니다.
변수 및 상수 만들기
일반적으로 변수는 매우 간단합니다. 여기에는 이름, 데이터가 있는 메모리 위치(또는 프로세서 레지스터) 및 해당 데이터 유형이 있습니다. 변수가 전역 변수인 경우 해당 부모는 컴파일 단위여야 하고, 로컬인 경우 해당 노드여야 합니다(프로시저 매개변수의 경우 특히 그렇습니다. 해당 부모는 프로시저 자체여야 합니다). 변수 선언이 있는 파일, 행, 열을 지정할 수도 있습니다.
가장 간단한 경우, 변수의 값은 고정된 주소에 위치하지만 스택이나 레지스터에 프로시저를 입력할 때 많은 변수가 동적으로 생성되므로 값의 주소를 계산하는 것이 매우 간단할 수 있습니다. 표준은 변수 값의 위치를 ​​설명하는 메커니즘(위치 표현)을 제공합니다. 주소 표현식은 Fort와 같은 스택 시스템을 위한 명령어 세트(DW_OP_xxxx 상수)이며 실제로는 분기, 프로시저 및 산술 연산이 포함된 별도의 언어입니다. 우리는 이 언어 전체를 검토하지는 않을 것입니다. 실제로는 몇 가지 지침에만 관심이 있습니다.
  • DW_OP_addr - 변수의 주소를 나타냅니다.
  • DW_OP_fbreg - 기본 레지스터(일반적으로 스택 포인터)로부터의 변수 오프셋을 나타냅니다.
  • DW_OP_reg0… DW_OP_reg31 - 변수가 해당 레지스터에 저장되어 있음을 나타냅니다.
주소 표현식을 생성하려면 먼저 빈 표현식(dwarf_new_expr)을 생성하고 이에 명령을 추가(dwarf_add_expr_addr, dwarf_add_expr_gen 등)한 후 DW_AT_location 속성 값(dwarf_add_AT_location_expression)으로 노드에 추가해야 합니다.
빈 주소 표현식을 생성하는 함수는 핸들을 반환하거나 오류가 발생하면 0을 반환합니다.
Dwarf_Expr dwarf_new_expr(Dwarf_P_Debug dbg, Dwarf_Error *error)
표현식에 명령을 추가하려면 dwarf_add_expr_gen 함수를 사용해야 합니다:
Dwarf_Unsigned dwarf_add_expr_gen(Dwarf_P_Expr expr, Dwarf_Small opcode, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *error)
  • opcode - 연산 코드, 상수 DW_OP_хххх
  • val1, val2 - 명령 매개변수(표준 참조)

변수의 주소를 명시적으로 설정하려면 이전 함수 대신 dwarf_add_expr_addr 함수를 사용해야 합니다.
Dwarf_Unsigned dwarf_add_expr_addr(Dwarf_P_Expr expr, Dwarf_Unsigned 주소, Dwarf_Signed Sym_index, Dwarf_Error *error)
  • expr은 명령어가 추가되는 주소 표현식에 대한 핸들입니다.
  • 주소 - 변수 주소
  • Sym_index - table.symtab에 있는 기호의 인덱스입니다. 선택 사항, 0을 전달할 수 있습니다.
또한 이 함수는 오류 시 DW_DLV_NOCOUNT를 반환합니다.
마지막으로 dwarf_add_AT_location_expr 함수를 사용하여 생성된 주소 표현식을 노드에 추가할 수 있습니다.
Dwarf_P_Attribute dwarf_add_AT_location_expr(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Expr loc_expr, Dwarf_Error *error)
  • ownerdie - 표현식이 추가되는 노드
  • attr - 속성(이 경우 DW_AT_location)
  • loc_expr - 이전에 생성된 주소 표현식에 대한 핸들
이 함수는 오류 발생 시 속성 핸들 또는 DW_DLV_NOCOUNT를 반환합니다.
변수(및 프로시저 매개변수)와 상수는 각각 DW_TAG_variable, DW_TAG_formal_parameter 및 DW_TAG_const_type 태그가 있는 일반 노드입니다. 다음 속성이 필요합니다.
  • 변수/상수 이름(dwarf_add_AT_name 함수, "노드 속성 생성" 참조)
  • 변수가 선언된 파일의 줄 번호(DW_AT_decl_line 속성), dwarf_add_AT_unsigned_const 함수(“노드 속성 생성” 참조)
  • 파일 이름 인덱스(DW_AT_decl_file 속성), dwarf_add_AT_unsigned_const 함수(“노드 속성 생성” 참조)
  • 변수/상수 데이터 유형(DW_AT_type 속성은 이전에 생성된 유형에 대한 링크입니다. "데이터 유형 생성" 참조)
  • 주소 표현식(위 참조) - 변수 또는 프로시저 매개변수에 필요합니다.
  • 또는 값 - 상수의 경우(DW_AT_const_value 속성, "노드 속성 생성" 참조)
디버깅 정보가 포함된 섹션 생성
디버깅 정보 트리의 모든 노드를 생성한 후 이를 사용하여 elf 섹션 생성을 시작할 수 있습니다. 이는 두 단계로 진행됩니다.
  • 먼저 dwarf_transform_to_disk_form 함수를 호출해야 합니다. 이 함수는 각 섹션에 대해 한 번씩 필요한 elf 섹션을 생성하기 위해 작성한 함수를 호출합니다.
  • 각 섹션에 대해 dwarf_get_section_bytes 함수는 해당 섹션에 작성해야 하는 데이터를 반환합니다.
기능
dwarf_transform_to_disk_form(Dwarf_P_Debug dbg, Dwarf_Error* 오류)
우리가 만든 디버깅 정보를 바이너리 형식으로 변환하지만 디스크에는 아무 것도 쓰지 않습니다. 생성된 엘프 섹션 수 또는 오류 발생 시 DW_DLV_NOCOUNT를 반환합니다. 이 경우 각 섹션에 대해 콜백 함수가 호출되며, 라이브러리를 초기화할 때 dwarf_producer_init_c 함수에 전달했습니다. 이 함수는 우리가 직접 작성해야 합니다. 그 사양은 다음과 같습니다.
typedef int (*Dwarf_Callback_Func_c)(char* 이름, int 크기, Dwarf_Unsigned 유형, Dwarf_Unsigned 플래그, Dwarf_Unsigned 링크, Dwarf_Unsigned 정보, Dwarf_Unsigned* sect_name_index, void * user_data, int* 오류)
  • name - 생성될 elf 섹션의 이름
  • 크기 - 섹션 크기
  • 유형 - 섹션 유형
  • 플래그 - 섹션 플래그
  • 링크 - 섹션 링크 필드
  • info - 섹션 정보 필드
  • sect_name_index - 재배치가 있는 섹션의 인덱스를 반환해야 합니다(선택 사항).
  • user_data - 라이브러리 초기화 함수에서 설정한 것과 동일한 방식으로 전달됩니다.
  • 오류 - 여기에서 오류 코드를 보낼 수 있습니다
이 기능에서는 다음을 수행해야 합니다.
  • 새 섹션 생성(elf_newscn 함수, 섹션 생성 참조)
  • 섹션 헤더 생성(elf32_getshdr 함수, ibid.)
  • 올바르게 작성하세요(ibid 참조). 섹션 헤더 필드가 함수의 매개변수에 해당하기 때문에 이는 쉽습니다. 누락된 필드 sh_addr, sh_offset, sh_entsize를 0으로, sh_addralign을 1로 설정합니다.
  • 생성된 섹션의 인덱스(elf_ndxscn 함수, “Section.symtab” 참조) 또는 오류 시 -1을 반환합니다(오류 코드를 error로 설정).
  • 또한 ".rel" 섹션(우리의 경우)을 건너뛰고 함수에서 반환할 때 0을 반환해야 합니다.
완료되면 dwarf_transform_to_disk_form 함수는 생성된 섹션 수를 반환합니다. 다음 단계에 따라 0부터 루프의 각 섹션을 거쳐야 합니다.
  • dwarf_get_section_bytes 함수를 사용하여 섹션에 쓸 데이터를 만듭니다.
    Dwarf_Ptr dwarf_get_section_bytes(Dwarf_P_Debug dbg, Dwarf_Signed dwarf_section, Dwarf_Signed *elf_section_index, Dwarf_Unsigned *length, Dwarf_Error* 오류)
    • dwarf_section - 섹션 번호. 0..n 범위에 있어야 합니다. 여기서 n은 dwarf_transform_to_disk_form 함수에 의해 반환된 숫자입니다.
    • elf_section_index - 데이터를 써야 하는 섹션의 인덱스를 반환합니다.
    • length - 이 데이터의 길이
    • 오류 - 사용되지 않음
    이 함수는 수신된 데이터에 대한 포인터 또는 0(이 경우
    더 이상 생성할 섹션이 남아 있지 않은 경우)
  • 현재 섹션에 대한 데이터 설명자를 만들고(elf_newdata 함수, 섹션 생성 참조) 다음을 설정하여 채웁니다(여기 참조).
    • d_buf - 이전 함수에서 받은 데이터에 대한 포인터
    • d_size - 이 데이터의 크기(ibid.)
도서관 작업 마무리
섹션이 형성된 후 dwarf_producer_finish 함수를 사용하여 libdwarf 작업을 완료할 수 있습니다.
Dwarf_Unsigned dwarf_producer_finish(Dwarf_P_Debug dbg, Dwarf_Error* 오류)
이 함수는 오류 시 DW_DLV_NOCOUNT를 반환합니다.
이 단계에서는 디스크에 기록이 수행되지 않습니다. 녹음은 "ELF 만들기 - 파일 쓰기" 섹션의 기능을 사용하여 수행해야 합니다.

결론

그게 다야.
반복합니다. 디버깅 정보 생성은 매우 광범위한 주제이므로 많은 주제를 다루지 않고 단지 베일을 벗었습니다. 원하는 사람은 무한정 더 깊이 들어갈 수 있습니다.
질문이 있으시면 답변해 드리겠습니다.

닫다