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