]>
Commit | Line | Data |
---|---|---|
fce2d171 | 1 | // stapdyn utility functions |
ef36f781 | 2 | // Copyright (C) 2014 Red Hat Inc. |
fce2d171 JS |
3 | // |
4 | // This file is part of systemtap, and is free software. You can | |
5 | // redistribute it and/or modify it under the terms of the GNU General | |
6 | // Public License (GPL); either version 2, or (at your option) any | |
7 | // later version. | |
8 | ||
51030d84 JS |
9 | #include "config.h" |
10 | ||
fce2d171 | 11 | #include <iostream> |
0ed66feb JS |
12 | #include <string> |
13 | #include <cstdlib> | |
14 | ||
15 | extern "C" { | |
fce2d171 | 16 | #include <dlfcn.h> |
0ed66feb JS |
17 | #include <err.h> |
18 | #include <link.h> | |
19 | } | |
20 | ||
51030d84 JS |
21 | #ifdef HAVE_SELINUX |
22 | #include <selinux/selinux.h> | |
23 | #endif | |
24 | ||
0ed66feb JS |
25 | #include "dynutil.h" |
26 | #include "../util.h" | |
27 | ||
28 | using namespace std; | |
29 | ||
30 | ||
31 | // Callback for dl_iterate_phdr to look for libdyninstAPI.so | |
32 | static int | |
33 | guess_dyninst_rt_callback(struct dl_phdr_info *info, | |
34 | size_t size __attribute__ ((unused)), | |
35 | void *data) | |
36 | { | |
37 | string& libdyninstAPI = *static_cast<string*>(data); | |
38 | ||
39 | const string name = info->dlpi_name ?: "(null)"; | |
40 | if (name.find("libdyninstAPI.so") != string::npos) | |
41 | libdyninstAPI = name; | |
42 | ||
43 | return 0; | |
44 | } | |
45 | ||
46 | // Look for libdyninstAPI.so in our own process, and use that | |
47 | // to guess the path for libdyninstAPI_RT.so | |
48 | static const string | |
49 | guess_dyninst_rt(void) | |
50 | { | |
51 | string libdyninstAPI; | |
52 | dl_iterate_phdr(guess_dyninst_rt_callback, &libdyninstAPI); | |
53 | ||
54 | string libdyninstAPI_RT; | |
55 | size_t so = libdyninstAPI.rfind(".so"); | |
56 | if (so != string::npos) | |
57 | { | |
58 | libdyninstAPI_RT = libdyninstAPI; | |
59 | libdyninstAPI_RT.insert(so, "_RT"); | |
60 | } | |
61 | return libdyninstAPI_RT; | |
62 | } | |
63 | ||
64 | // Check that environment DYNINSTAPI_RT_LIB exists and is a valid file. | |
65 | // If not, try to guess a good value and set it. | |
66 | bool | |
67 | check_dyninst_rt(void) | |
68 | { | |
69 | static const char rt_env_name[] = "DYNINSTAPI_RT_LIB"; | |
70 | const char* rt_env = getenv(rt_env_name); | |
71 | if (rt_env) | |
72 | { | |
73 | if (file_exists(rt_env)) | |
74 | return true; | |
145004e9 | 75 | staperror() << "Invalid " << rt_env_name << ": \"" << rt_env << "\"" << endl; |
0ed66feb JS |
76 | } |
77 | ||
78 | const string rt = guess_dyninst_rt(); | |
79 | if (rt.empty() || !file_exists(rt)) | |
80 | { | |
145004e9 | 81 | staperror() << "Can't find libdyninstAPI_RT.so; try setting " << rt_env_name << endl; |
0ed66feb JS |
82 | return false; |
83 | } | |
84 | ||
85 | if (setenv(rt_env_name, rt.c_str(), 1) != 0) | |
86 | { | |
145004e9 JL |
87 | int olderrno = errno; |
88 | staperror() << "Can't set " << rt_env_name << ": " << strerror(olderrno); | |
0ed66feb JS |
89 | return false; |
90 | } | |
91 | ||
92 | return true; | |
93 | } | |
94 | ||
95 | ||
51030d84 JS |
96 | // Check that SELinux settings are ok for Dyninst operation. |
97 | bool | |
56098c79 | 98 | check_dyninst_sebools(bool attach) |
51030d84 JS |
99 | { |
100 | #ifdef HAVE_SELINUX | |
101 | // For all these checks, we could examine errno on failure to act differently | |
102 | // for e.g. ENOENT vs. EPERM. But since these are just early diagnostices, | |
103 | // I'm only going worry about successful bools for now. | |
104 | ||
105 | // deny_ptrace is definitely a blocker for us to attach at all | |
106 | if (security_get_boolean_active("deny_ptrace") > 0) | |
107 | { | |
145004e9 JL |
108 | staperror() << "SELinux boolean 'deny_ptrace' is active, " |
109 | "which blocks Dyninst" << endl; | |
51030d84 JS |
110 | return false; |
111 | } | |
112 | ||
113 | // We might have to get more nuanced about allow_execstack, especially if | |
114 | // Dyninst is later enhanced to work around this restriction. But for now, | |
115 | // this is also a blocker. | |
116 | if (security_get_boolean_active("allow_execstack") == 0) | |
117 | { | |
145004e9 JL |
118 | staperror() << "SELinux boolean 'allow_execstack' is disabled, " |
119 | "which blocks Dyninst" << endl; | |
51030d84 JS |
120 | return false; |
121 | } | |
56098c79 JS |
122 | |
123 | // In process-attach mode, SELinux will trigger "avc: denied { execmod }" | |
124 | // on ld.so, when the mutator is injecting the dlopen for libdyninstAPI_RT.so. | |
125 | if (attach && security_get_boolean_active("allow_execmod") == 0) | |
126 | { | |
145004e9 JL |
127 | staperror() << "SELinux boolean 'allow_execmod' is disabled, " |
128 | "which blocks Dyninst" << endl; | |
56098c79 JS |
129 | return false; |
130 | } | |
145ceff0 JS |
131 | #else |
132 | (void)attach; // unused | |
51030d84 JS |
133 | #endif |
134 | ||
135 | return true; | |
136 | } | |
137 | ||
138 | ||
3d381237 JS |
139 | // Check whether a process exited cleanly |
140 | bool | |
141 | check_dyninst_exit(BPatch_process *process) | |
142 | { | |
143 | int code; | |
144 | switch (process->terminationStatus()) | |
145 | { | |
146 | case ExitedNormally: | |
147 | code = process->getExitCode(); | |
148 | if (code == EXIT_SUCCESS) | |
149 | return true; | |
0d06fa0d | 150 | stapwarn() << "Child process exited with status " << code << endl; |
3d381237 JS |
151 | return false; |
152 | ||
153 | case ExitedViaSignal: | |
154 | code = process->getExitSignal(); | |
0d06fa0d JL |
155 | stapwarn() << "Child process exited with signal " << code |
156 | << " (" << strsignal(code) << ")" << endl; | |
3d381237 JS |
157 | return false; |
158 | ||
6d27dcff JS |
159 | case NoExit: |
160 | if (process->isTerminated()) | |
0d06fa0d | 161 | stapwarn() << "Child process exited in an unknown manner" << endl; |
6d27dcff | 162 | else |
0d06fa0d | 163 | stapwarn() << "Child process has not exited" << endl; |
6d27dcff JS |
164 | return false; |
165 | ||
3d381237 JS |
166 | default: |
167 | return false; | |
168 | } | |
169 | } | |
170 | ||
171 | ||
fce2d171 JS |
172 | // Get an untyped symbol from a dlopened module. |
173 | // If flagged as 'required', throw an exception if missing or NULL. | |
174 | void * | |
175 | get_dlsym(void* handle, const char* symbol, bool required) | |
176 | { | |
177 | const char* err = dlerror(); // clear previous errors | |
178 | void *pointer = dlsym(handle, symbol); | |
179 | if (required) | |
180 | { | |
181 | if ((err = dlerror())) | |
182 | throw std::runtime_error("dlsym " + std::string(err)); | |
183 | if (pointer == NULL) | |
184 | throw std::runtime_error("dlsym " + std::string(symbol) + " is NULL"); | |
185 | } | |
186 | return pointer; | |
187 | } | |
188 | ||
189 | ||
190 | // | |
191 | // Logging, warnings, and errors, oh my! | |
192 | // | |
193 | ||
194 | // A null-sink output stream, similar to /dev/null | |
195 | // (no buffer -> badbit -> quietly suppressed output) | |
196 | static ostream nullstream(NULL); | |
197 | ||
198 | // verbosity, increased by -v | |
199 | unsigned stapdyn_log_level = 0; | |
200 | ||
201 | // Whether to suppress warnings, set by -w | |
ac033e87 | 202 | bool stapdyn_suppress_warnings = false; |
fce2d171 | 203 | |
6d842dc7 DS |
204 | // Output file name, set by -o |
205 | char *stapdyn_outfile_name = NULL; | |
206 | ||
fce2d171 JS |
207 | // Return a stream for logging at the given verbosity level. |
208 | ostream& | |
209 | staplog(unsigned level) | |
210 | { | |
211 | if (level > stapdyn_log_level) | |
212 | return nullstream; | |
213 | return clog << program_invocation_short_name << ": "; | |
214 | } | |
215 | ||
216 | // Return a stream for warning messages. | |
217 | ostream& | |
218 | stapwarn(void) | |
219 | { | |
ac033e87 | 220 | if (stapdyn_suppress_warnings) |
fce2d171 | 221 | return nullstream; |
bfbd8838 JL |
222 | return clog << program_invocation_short_name << ": " |
223 | << colorize("WARNING:", "warning") << " "; | |
fce2d171 JS |
224 | } |
225 | ||
226 | // Return a stream for error messages. | |
227 | ostream& | |
228 | staperror(void) | |
229 | { | |
bfbd8838 JL |
230 | return clog << program_invocation_short_name << ": " |
231 | << colorize("ERROR:", "error") << " "; | |
fce2d171 JS |
232 | } |
233 | ||
89fd17f5 JL |
234 | // Whether to color error and warning messages |
235 | bool color_errors; // Initialized in main() | |
fce2d171 | 236 | |
bfbd8838 JL |
237 | // Adds coloring to strings |
238 | std::string | |
239 | colorize(std::string str, std::string type) | |
240 | { | |
241 | if (str.empty() || !color_errors) | |
242 | return str; | |
243 | else { | |
244 | // Check if this type is defined in SYSTEMTAP_COLORS | |
245 | std::string color = parse_stap_color(type); | |
246 | if (!color.empty()) // no need to pollute terminal if not necessary | |
247 | return "\033[" + color + "m\033[K" + str + "\033[m\033[K"; | |
248 | else | |
249 | return str; | |
250 | } | |
251 | } | |
252 | ||
253 | /* Parse SYSTEMTAP_COLORS and returns the SGR parameter(s) for the given | |
254 | type. The env var SYSTEMTAP_COLORS must be in the following format: | |
255 | 'key1=val1:key2=val2:' etc... where valid keys are 'error', 'warning', | |
256 | 'source', 'caret', 'token' and valid values constitute SGR parameter(s). | |
257 | For example, the default setting would be: | |
258 | 'error=01;31:warning=00;33:source=00;34:caret=01:token=01' | |
259 | */ | |
260 | std::string | |
261 | parse_stap_color(std::string type) | |
262 | { | |
263 | const char *key, *col, *eq; | |
264 | int n = type.size(); | |
265 | int done = 0; | |
266 | ||
267 | key = getenv("SYSTEMTAP_COLORS"); | |
6667be37 | 268 | if (key == NULL) |
bfbd8838 | 269 | key = "error=01;31:warning=00;33:source=00;34:caret=01:token=01"; |
6667be37 JL |
270 | else if (*key == '\0') |
271 | return ""; // disable colors if set but empty | |
bfbd8838 JL |
272 | |
273 | while (!done) { | |
274 | if (!(col = strchr(key, ':'))) { | |
275 | col = strchr(key, '\0'); | |
276 | done = 1; | |
277 | } | |
278 | if (!((eq = strchr(key, '=')) && eq < col)) | |
279 | return ""; /* invalid syntax: no = in range */ | |
280 | if (!(key < eq && eq < col-1)) | |
281 | return ""; /* invalid syntax: key or val empty */ | |
282 | if (strspn(eq+1, "0123456789;") < (size_t)(col-eq-1)) | |
283 | return ""; /* invalid syntax: invalid char in val */ | |
284 | if (eq-key == n && type.compare(0, n, key, n) == 0) | |
285 | return string(eq+1, col-eq-1); | |
286 | if (!done) key = col+1; /* advance to next key */ | |
287 | } | |
288 | ||
289 | // Could not find the key | |
290 | return ""; | |
291 | } | |
292 | ||
0ed66feb | 293 | /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ |