1/*
2 * Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "elf_haiku_version.h"
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <image_defs.h>
13#include <syscalls.h>
14
15#include "elf_symbol_lookup.h"
16
17
18// interim Haiku API versions
19#define HAIKU_VERSION_PRE_GLUE_CODE		0x00000010
20
21
22static bool
23analyze_object_gcc_version(int fd, image_t* image, elf_ehdr& eheader,
24	int32 sheaderSize, char* buffer, size_t bufferSize)
25{
26	if (sheaderSize <= 0)
27		return false;
28
29	if (sheaderSize > (int)bufferSize) {
30		FATAL("%s: Cannot handle section headers bigger than %lu bytes\n",
31			image->path, bufferSize);
32		return false;
33	}
34
35	// read section headers
36	ssize_t length = _kern_read(fd, eheader.e_shoff, buffer, sheaderSize);
37	if (length != sheaderSize) {
38		FATAL("%s: Could not read section headers: %s\n", image->path,
39			strerror(length));
40		return false;
41	}
42
43	// load the string section
44	elf_shdr* sectionHeader
45		= (elf_shdr*)(buffer + eheader.e_shstrndx * eheader.e_shentsize);
46
47	if (sheaderSize + sectionHeader->sh_size > bufferSize) {
48		FATAL("%s: Buffer not big enough for section string section\n",
49			image->path);
50		return false;
51	}
52
53	char* sectionStrings = buffer + bufferSize - sectionHeader->sh_size;
54	length = _kern_read(fd, sectionHeader->sh_offset, sectionStrings,
55		sectionHeader->sh_size);
56	if (length != (int)sectionHeader->sh_size) {
57		FATAL("%s: Could not read section string section: %s\n", image->path,
58			strerror(length));
59		return false;
60	}
61
62	// find the .comment section
63	off_t commentOffset = 0;
64	size_t commentSize = 0;
65	for (uint32 i = 0; i < eheader.e_shnum; i++) {
66		sectionHeader = (elf_shdr*)(buffer + i * eheader.e_shentsize);
67		const char* sectionName = sectionStrings + sectionHeader->sh_name;
68		if (sectionHeader->sh_name != 0
69			&& strcmp(sectionName, ".comment") == 0) {
70			commentOffset = sectionHeader->sh_offset;
71			commentSize = sectionHeader->sh_size;
72			break;
73		}
74	}
75
76	if (commentSize == 0) {
77		FATAL("%s: Could not find .comment section\n", image->path);
78		return false;
79	}
80
81	// read a part of the comment section
82	if (commentSize > 512)
83		commentSize = 512;
84
85	length = _kern_read(fd, commentOffset, buffer, commentSize);
86	if (length != (int)commentSize) {
87		FATAL("%s: Could not read .comment section: %s\n", image->path,
88			strerror(length));
89		return false;
90	}
91
92	// the common prefix of the strings in the .comment section
93	static const char* kGCCVersionPrefix = "GCC: (";
94	size_t gccVersionPrefixLen = strlen(kGCCVersionPrefix);
95
96	size_t index = 0;
97	int gccMajor = 0;
98	int gccMiddle = 0;
99	int gccMinor = 0;
100	bool isHaiku = true;
101
102	// Read up to 10 comments. The first three or four are usually from the
103	// glue code.
104	for (int i = 0; i < 10; i++) {
105		// skip '\0'
106		while (index < commentSize && buffer[index] == '\0')
107			index++;
108		char* stringStart = buffer + index;
109
110		// find string end
111		while (index < commentSize && buffer[index] != '\0')
112			index++;
113
114		// ignore the entry at the end of the buffer
115		if (index == commentSize)
116			break;
117
118		// We have to analyze string like these:
119		// GCC: (GNU) 2.9-beos-991026
120		// GCC: (GNU) 2.95.3-haiku-080322
121		// GCC: (GNU) 4.1.2
122		// GCC: (2016_02_29) 5.3.0
123		// GCC: (2018_05_01) 7.3.0
124		// GCC: (GNU) 7.3.0
125
126		// FIXME this does not handle binaries generated with clang or other
127		// compilers.
128
129		// skip the common prefix
130		if (strncmp(stringStart, kGCCVersionPrefix, gccVersionPrefixLen) != 0)
131			continue;
132
133		// Skip the build identifier, the closing parenthesis, and the space
134		// that follows it.
135		// Hopefully no one is going to include nested parentheses in the
136		// version string, so we can save the need for a smarter parser.
137		char* gccVersion = strchr(stringStart + gccVersionPrefixLen, ')') + 2;
138
139		// the rest is the GCC version
140		char* gccPlatform = strchr(gccVersion, '-');
141		char* patchLevel = NULL;
142		if (gccPlatform != NULL) {
143			*gccPlatform = '\0';
144			gccPlatform++;
145			patchLevel = strchr(gccPlatform, '-');
146			if (patchLevel != NULL) {
147				*patchLevel = '\0';
148				patchLevel++;
149			}
150		}
151
152		// split the gcc version into major, middle, and minor
153		int version[3] = { 0, 0, 0 };
154
155		for (int k = 0; gccVersion != NULL && k < 3; k++) {
156			char* dot = strchr(gccVersion, '.');
157			if (dot) {
158				*dot = '\0';
159				dot++;
160			}
161			version[k] = atoi(gccVersion);
162			gccVersion = dot;
163		}
164
165		// got any version?
166		if (version[0] == 0)
167			continue;
168
169		// Select the gcc version with the smallest major, but the greatest
170		// middle/minor. This should usually ignore the glue code version as
171		// well as cases where e.g. in a gcc 2 program a single C file has
172		// been compiled with gcc 4.
173		if (gccMajor == 0 || gccMajor > version[0]
174			|| (gccMajor == version[0]
175				&& (gccMiddle < version[1]
176					|| (gccMiddle == version[1] && gccMinor < version[2])))) {
177			gccMajor = version[0];
178			gccMiddle = version[1];
179			gccMinor = version[2];
180		}
181
182		if (gccMajor == 2 && gccPlatform != NULL
183			&& strcmp(gccPlatform, "haiku")) {
184			isHaiku = false;
185		}
186	}
187
188	if (gccMajor == 0)
189		return false;
190
191	if (gccMajor == 2) {
192		if (gccMiddle < 95)
193			image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
194		else if (isHaiku)
195			image->abi = B_HAIKU_ABI_GCC_2_HAIKU;
196		else
197			image->abi = B_HAIKU_ABI_GCC_2_BEOS;
198	} else {
199		if (gccMajor >= 5) {
200			// The ABI changes in libstdc++ 5+ are optional, and currently we
201			// are using it in backwards compatible mode. So, it is still
202			// generating ABI version 4.
203			gccMajor = 4;
204		}
205		image->abi = gccMajor << 16;
206	}
207
208	return true;
209}
210
211
212void
213analyze_image_haiku_version_and_abi(int fd, image_t* image, elf_ehdr& eheader,
214	int32 sheaderSize, char* buffer, size_t bufferSize)
215{
216	// Haiku API version
217	elf_sym* symbol = find_symbol(image,
218		SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_VERSION_VARIABLE_NAME,
219			B_SYMBOL_TYPE_DATA, true));
220	if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
221		&& symbol->st_value > 0
222		&& symbol->st_size >= sizeof(uint32)) {
223		image->api_version
224			= *(uint32*)(symbol->st_value + image->regions[0].delta);
225	} else
226		image->api_version = 0;
227
228	// Haiku ABI
229	symbol = find_symbol(image,
230		SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_ABI_VARIABLE_NAME,
231			B_SYMBOL_TYPE_DATA));
232	if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
233		&& symbol->st_value > 0
234		&& symbol->Type() == STT_OBJECT
235		&& symbol->st_size >= sizeof(uint32)) {
236		image->abi = *(uint32*)(symbol->st_value + image->regions[0].delta);
237	} else
238		image->abi = 0;
239
240	if (image->abi == 0) {
241		// No ABI version in the shared object, i.e. it has been built before
242		// that was introduced in Haiku. We have to try and analyze the gcc
243		// version.
244		if (!analyze_object_gcc_version(fd, image, eheader, sheaderSize,
245				buffer, bufferSize)) {
246			FATAL("%s: Failed to get gcc version.\n", image->path);
247				// not really fatal, actually
248
249			// assume ancient BeOS
250			image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
251		}
252	}
253
254	// guess the API version, if we couldn't figure it out yet
255	if (image->api_version == 0) {
256		image->api_version = image->abi > B_HAIKU_ABI_GCC_2_BEOS
257			? HAIKU_VERSION_PRE_GLUE_CODE : B_HAIKU_VERSION_BEOS;
258	}
259}
260