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
analyze_object_gcc_version(int fd,image_t * image,elf_ehdr & eheader,int32 sheaderSize,char * buffer,size_t bufferSize)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 <= 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
212 void
analyze_image_haiku_version_and_abi(int fd,image_t * image,elf_ehdr & eheader,int32 sheaderSize,char * buffer,size_t bufferSize)213 analyze_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));
220 if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
221 && symbol->st_value > 0
222 && symbol->Type() == STT_OBJECT
223 && symbol->st_size >= sizeof(uint32)) {
224 image->api_version
225 = *(uint32*)(symbol->st_value + image->regions[0].delta);
226 } else
227 image->api_version = 0;
228
229 // Haiku ABI
230 symbol = find_symbol(image,
231 SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_ABI_VARIABLE_NAME,
232 B_SYMBOL_TYPE_DATA));
233 if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
234 && symbol->st_value > 0
235 && symbol->Type() == STT_OBJECT
236 && symbol->st_size >= sizeof(uint32)) {
237 image->abi = *(uint32*)(symbol->st_value + image->regions[0].delta);
238 } else
239 image->abi = 0;
240
241 if (image->abi == 0) {
242 // No ABI version in the shared object, i.e. it has been built before
243 // that was introduced in Haiku. We have to try and analyze the gcc
244 // version.
245 if (!analyze_object_gcc_version(fd, image, eheader, sheaderSize,
246 buffer, bufferSize)) {
247 FATAL("%s: Failed to get gcc version.\n", image->path);
248 // not really fatal, actually
249
250 // assume ancient BeOS
251 image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
252 }
253 }
254
255 // guess the API version, if we couldn't figure it out yet
256 if (image->api_version == 0) {
257 image->api_version = image->abi > B_HAIKU_ABI_GCC_2_BEOS
258 ? HAIKU_VERSION_PRE_GLUE_CODE : B_HAIKU_VERSION_BEOS;
259 }
260 }
261