xref: /haiku/src/system/runtime_loader/elf_haiku_version.cpp (revision b247f935d133a42c427cad8a759a1bf2f65bc290)
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 
121 		// FIXME this does not handle binaries generated with clang or other
122 		// compilers.
123 
124 		// skip the common prefix
125 		if (strncmp(stringStart, kGCCVersionPrefix, gccVersionPrefixLen) != 0)
126 			continue;
127 
128 		// Skip the build identifier, the closing parenthesis, and the space
129 		// that follows it.
130 		// Hopefully no one is going to include nested parentheses in the
131 		// version string, so we can save the need for a smarter parser.
132 		char* gccVersion = strchr(stringStart + gccVersionPrefixLen, ')') + 2;
133 
134 		// the rest is the GCC version
135 		char* gccPlatform = strchr(gccVersion, '-');
136 		char* patchLevel = NULL;
137 		if (gccPlatform != NULL) {
138 			*gccPlatform = '\0';
139 			gccPlatform++;
140 			patchLevel = strchr(gccPlatform, '-');
141 			if (patchLevel != NULL) {
142 				*patchLevel = '\0';
143 				patchLevel++;
144 			}
145 		}
146 
147 		// split the gcc version into major, middle, and minor
148 		int version[3] = { 0, 0, 0 };
149 
150 		for (int k = 0; gccVersion != NULL && k < 3; k++) {
151 			char* dot = strchr(gccVersion, '.');
152 			if (dot) {
153 				*dot = '\0';
154 				dot++;
155 			}
156 			version[k] = atoi(gccVersion);
157 			gccVersion = dot;
158 		}
159 
160 		// got any version?
161 		if (version[0] == 0)
162 			continue;
163 
164 		// Select the gcc version with the smallest major, but the greatest
165 		// middle/minor. This should usually ignore the glue code version as
166 		// well as cases where e.g. in a gcc 2 program a single C file has
167 		// been compiled with gcc 4.
168 		if (gccMajor == 0 || gccMajor > version[0]
169 			|| (gccMajor == version[0]
170 				&& (gccMiddle < version[1]
171 					|| (gccMiddle == version[1] && gccMinor < version[2])))) {
172 			gccMajor = version[0];
173 			gccMiddle = version[1];
174 			gccMinor = version[2];
175 		}
176 
177 		if (gccMajor == 2 && gccPlatform != NULL
178 			&& strcmp(gccPlatform, "haiku")) {
179 			isHaiku = false;
180 		}
181 	}
182 
183 	if (gccMajor == 0)
184 		return false;
185 
186 	if (gccMajor == 2) {
187 		if (gccMiddle < 95)
188 			image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
189 		else if (isHaiku)
190 			image->abi = B_HAIKU_ABI_GCC_2_HAIKU;
191 		else
192 			image->abi = B_HAIKU_ABI_GCC_2_BEOS;
193 	} else {
194 		if (gccMajor == 5) {
195 			// The ABI changes in GCC 5 are optional, and currently we are using
196 			// it in backwards compatible mode. So, it is still generating ABI
197 			// version 4.
198 			gccMajor = 4;
199 		}
200 		image->abi = gccMajor << 16;
201 	}
202 
203 	return true;
204 }
205 
206 
207 void
208 analyze_image_haiku_version_and_abi(int fd, image_t* image, elf_ehdr& eheader,
209 	int32 sheaderSize, char* buffer, size_t bufferSize)
210 {
211 	// Haiku API version
212 	elf_sym* symbol = find_symbol(image,
213 		SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_VERSION_VARIABLE_NAME,
214 			B_SYMBOL_TYPE_DATA, true));
215 	if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
216 		&& symbol->st_value > 0
217 		&& symbol->st_size >= sizeof(uint32)) {
218 		image->api_version
219 			= *(uint32*)(symbol->st_value + image->regions[0].delta);
220 	} else
221 		image->api_version = 0;
222 
223 	// Haiku ABI
224 	symbol = find_symbol(image,
225 		SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_ABI_VARIABLE_NAME,
226 			B_SYMBOL_TYPE_DATA));
227 	if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
228 		&& symbol->st_value > 0
229 		&& symbol->Type() == STT_OBJECT
230 		&& symbol->st_size >= sizeof(uint32)) {
231 		image->abi = *(uint32*)(symbol->st_value + image->regions[0].delta);
232 	} else
233 		image->abi = 0;
234 
235 	if (image->abi == 0) {
236 		// No ABI version in the shared object, i.e. it has been built before
237 		// that was introduced in Haiku. We have to try and analyze the gcc
238 		// version.
239 		if (!analyze_object_gcc_version(fd, image, eheader, sheaderSize,
240 				buffer, bufferSize)) {
241 			FATAL("%s: Failed to get gcc version.\n", image->path);
242 				// not really fatal, actually
243 
244 			// assume ancient BeOS
245 			image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
246 		}
247 	}
248 
249 	// guess the API version, if we couldn't figure it out yet
250 	if (image->api_version == 0) {
251 		image->api_version = image->abi > B_HAIKU_ABI_GCC_2_BEOS
252 			? HAIKU_VERSION_PRE_GLUE_CODE : B_HAIKU_VERSION_BEOS;
253 	}
254 }
255