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