xref: /haiku/src/system/runtime_loader/elf_haiku_version.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 
22 static bool
23 analyze_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 
209 void
210 analyze_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