1 : // Copyright 2012 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/agent/asan/asan_runtime.h"
16 :
17 : #include "base/bind.h"
18 : #include "base/command_line.h"
19 : #include "base/environment.h"
20 : #include "base/logging.h"
21 : #include "base/string_number_conversions.h"
22 : #include "base/string_tokenizer.h"
23 : #include "base/sys_string_conversions.h"
24 : #include "base/utf_string_conversions.h"
25 : #include "syzygy/agent/asan/asan_logger.h"
26 : #include "syzygy/agent/asan/stack_capture_cache.h"
27 : #include "syzygy/trace/protocol/call_trace_defs.h"
28 :
29 : namespace agent {
30 : namespace asan {
31 :
32 : namespace {
33 :
34 : using agent::asan::AsanLogger;
35 : using agent::asan::HeapProxy;
36 : using agent::asan::StackCaptureCache;
37 :
38 : // The default error handler.
39 : // @param context The context when the error has been reported.
40 : // @param stack_id The id of the crash stack trace.
41 i : void OnAsanError(CONTEXT* context) {
42 i : ::DebugBreak();
43 i : ::RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
44 i : }
45 :
46 : // Try to update the value of a size_t variable from a command-line.
47 : // @param cmd_line The command line who might contain a given parameter.
48 : // @param param_name The parameter that we want to read.
49 : // @param value Will receive the value of the parameter if it's present.
50 : // @returns true on success, false otherwise.
51 : bool UpdateSizetFromCommandLine(const CommandLine& cmd_line,
52 : const std::string& param_name,
53 E : size_t* value) {
54 E : DCHECK(value != NULL);
55 E : if (!cmd_line.HasSwitch(param_name))
56 E : return true;
57 E : std::string value_str = cmd_line.GetSwitchValueASCII(param_name);
58 E : size_t new_value = 0;
59 E : if (!base::StringToSizeT(value_str, &new_value))
60 i : return false;
61 E : *value = new_value;
62 :
63 E : return true;
64 E : }
65 :
66 : // Try to update the value of an array of ignored stack ids from a command-line.
67 : // We expect the values to be in hexadecimal format and separated by a
68 : // semi-colon.
69 : // @param cmd_line The command line to parse.
70 : // @param param_name The parameter that we want to read.
71 : // @param values Will receive the set of parsed values.
72 : // @returns true on success, false otherwise.
73 : bool ReadIgnoredStackIdsFromCommandLine(const CommandLine& cmd_line,
74 : const std::string& param_name,
75 E : AsanRuntime::StackIdSet* values) {
76 E : DCHECK(values != NULL);
77 E : if (!cmd_line.HasSwitch(param_name))
78 E : return true;
79 E : std::string value_str = cmd_line.GetSwitchValueASCII(param_name);
80 E : StringTokenizer string_tokenizer(value_str, ";");
81 E : while (string_tokenizer.GetNext()) {
82 E : int new_value = 0;
83 E : if (!base::HexStringToInt(string_tokenizer.token(), &new_value))
84 i : return false;
85 E : values->insert(static_cast<StackCapture::StackId>(new_value));
86 E : }
87 E : return true;
88 E : }
89 :
90 : // A helper function to find if an intrusive list contains a given entry.
91 : // @param list The list in which we want to look for the entry.
92 : // @param item The entry we want to look for.
93 : // @returns true if the list contains this entry, false otherwise.
94 E : bool HeapListContainsEntry(const LIST_ENTRY* list, const LIST_ENTRY* item) {
95 E : LIST_ENTRY* current = list->Flink;
96 E : while (current != NULL) {
97 E : LIST_ENTRY* next_item = NULL;
98 E : if (current->Flink != list) {
99 E : next_item = current->Flink;
100 : }
101 :
102 E : if (current == item) {
103 E : return true;
104 : }
105 :
106 E : current = next_item;
107 E : }
108 i : return false;
109 E : }
110 :
111 : } // namespace
112 :
113 : const char AsanRuntime::kSyzyAsanEnvVar[] = "ASAN_OPTIONS";
114 :
115 : const char AsanRuntime::kBottomFramesToSkip[] =
116 : "bottom_frames_to_skip";
117 : const char AsanRuntime::kQuarantineSize[] = "quarantine_size";
118 : const char AsanRuntime::kCompressionReportingPeriod[] =
119 : "compression_reporting_period";
120 : const char AsanRuntime::kMaxNumberOfFrames[] = "max_num_frames";
121 : const char AsanRuntime::kIgnoredStackIds[] = "ignored_stack_ids";
122 : const wchar_t AsanRuntime::kSyzyAsanDll[] = L"asan_rtl.dll";
123 :
124 E : AsanRuntime::AsanRuntime() : logger_(NULL), stack_cache_(NULL) {
125 E : }
126 :
127 E : AsanRuntime::~AsanRuntime() {
128 E : }
129 :
130 E : void AsanRuntime::SetUp(const std::wstring& flags_command_line) {
131 : // Initialize the command-line structures. This is needed so that
132 : // SetUpLogger() can include the command-line in the message announcing
133 : // this process. Note: this is mostly for debugging purposes.
134 E : CommandLine::Init(0, NULL);
135 :
136 E : InitializeListHead(&heap_proxy_dlist_);
137 :
138 : // Setup the "global" state.
139 E : SetUpLogger();
140 E : SetUpStackCache();
141 E : if (!ParseFlagsFromString(flags_command_line)) {
142 i : LOG(ERROR) << "Unable to parse the flags from the input string (\""
143 : << flags_command_line.c_str() << "\").";
144 : }
145 : // Propagates the flags values to the different modules.
146 E : PropagateFlagsValues();
147 :
148 : // Use the default callback.
149 E : SetErrorCallBack(&OnAsanError);
150 E : }
151 :
152 E : void AsanRuntime::TearDown() {
153 E : TearDownStackCache();
154 E : TearDownLogger();
155 E : DCHECK(asan_error_callback_.is_null() == FALSE);
156 E : asan_error_callback_.Reset();
157 : // In principle, we should also check that all the heaps have been destroyed
158 : // but this is not guaranteed to be the case in Chrome, so the heap list may
159 : // not be empty here.
160 E : }
161 :
162 E : void AsanRuntime::OnError(CONTEXT* context) {
163 E : DCHECK(context != NULL);
164 : // Call the callback to handle this error.
165 E : DCHECK_EQ(false, asan_error_callback_.is_null());
166 E : asan_error_callback_.Run(context);
167 E : }
168 :
169 E : void AsanRuntime::SetErrorCallBack(void (*callback)(CONTEXT*)) {
170 E : asan_error_callback_ = base::Bind(callback);
171 E : }
172 :
173 E : void AsanRuntime::SetUpLogger() {
174 : // Setup variables we're going to use.
175 E : scoped_ptr<base::Environment> env(base::Environment::Create());
176 E : scoped_ptr<AsanLogger> client(new AsanLogger);
177 E : CHECK(env.get() != NULL);
178 E : CHECK(client.get() != NULL);
179 :
180 : // Initialize the client.
181 E : std::string instance_id;
182 E : if (env->GetVar(kSyzygyRpcInstanceIdEnvVar, &instance_id))
183 E : client->set_instance_id(UTF8ToWide(instance_id));
184 E : client->Init();
185 :
186 : // Register the client singleton instance.
187 E : logger_.reset(client.release());
188 E : }
189 :
190 E : void AsanRuntime::TearDownLogger() {
191 E : logger_.reset();
192 E : }
193 :
194 E : void AsanRuntime::SetUpStackCache() {
195 E : DCHECK(stack_cache_.get() == NULL);
196 E : DCHECK(logger_.get() != NULL);
197 E : stack_cache_.reset(new StackCaptureCache(logger_.get()));
198 E : }
199 :
200 E : void AsanRuntime::TearDownStackCache() {
201 E : DCHECK(stack_cache_.get() != NULL);
202 E : stack_cache_->LogCompressionRatio();
203 E : stack_cache_.reset();
204 E : }
205 :
206 E : bool AsanRuntime::ParseFlagsFromString(std::wstring str) {
207 : // Prepends the flags with the agent name. We need to do this because the
208 : // command-line constructor expect the process name to be the first value of
209 : // the command-line string.
210 : // Start by inserting a space at the beginning of the flags to separate the
211 : // flags from the agent name.
212 E : str.insert(0, L" ");
213 : // Insert the agent name.
214 E : str.insert(0, kSyzyAsanDll);
215 :
216 E : CommandLine cmd_line = CommandLine::FromString(str);
217 :
218 : // Parse the quarantine size flag.
219 E : flags_.quarantine_size = HeapProxy::default_quarantine_max_size();
220 : if (!UpdateSizetFromCommandLine(cmd_line, kQuarantineSize,
221 E : &flags_.quarantine_size)) {
222 i : LOG(ERROR) << "Unable to read " << kQuarantineSize << " from the argument "
223 : << "list.";
224 i : return false;
225 : }
226 :
227 : // Parse the reporting period flag.
228 : flags_.reporting_period =
229 E : StackCaptureCache::GetDefaultCompressionReportingPeriod();
230 : if (!UpdateSizetFromCommandLine(cmd_line, kCompressionReportingPeriod,
231 E : &flags_.reporting_period)) {
232 i : LOG(ERROR) << "Unable to read " << kCompressionReportingPeriod
233 : << " from the argument list.";
234 i : return false;
235 : }
236 :
237 : // Parse the bottom frames to skip flag.
238 E : flags_.bottom_frames_to_skip = StackCapture::bottom_frames_to_skip();
239 : if (!UpdateSizetFromCommandLine(cmd_line, kBottomFramesToSkip,
240 E : &flags_.bottom_frames_to_skip)) {
241 i : LOG(ERROR) << "Unable to read " << kBottomFramesToSkip << " from the "
242 : << "argument list.";
243 i : return false;
244 : }
245 :
246 : // Parse the max number of frames flag.
247 E : flags_.max_num_frames = stack_cache_->max_num_frames();
248 : if (!UpdateSizetFromCommandLine(cmd_line, kMaxNumberOfFrames,
249 E : &flags_.max_num_frames)) {
250 i : LOG(ERROR) << "Unable to read " << kMaxNumberOfFrames << " from the "
251 : << "argument list.";
252 i : return false;
253 : }
254 :
255 : // Parse the ignored stack ids.
256 : if (!ReadIgnoredStackIdsFromCommandLine(cmd_line, kIgnoredStackIds,
257 E : &flags_.ignored_stack_ids)) {
258 i : LOG(ERROR) << "Unable to read " << kIgnoredStackIds << " from the "
259 : << "argument list.";
260 i : return false;
261 : }
262 :
263 E : return true;
264 E : }
265 :
266 E : bool AsanRuntime::GetAsanFlagsEnvVar(std::wstring* env_var_wstr) {
267 E : scoped_ptr<base::Environment> env(base::Environment::Create());
268 E : if (env.get() == NULL) {
269 i : LOG(ERROR) << "base::Environment::Create returned NULL.";
270 i : return false;
271 : }
272 :
273 : // If this fails, the environment variable simply does not exist.
274 E : std::string env_var_str;
275 E : if (!env->GetVar(kSyzyAsanEnvVar, &env_var_str)) {
276 E : return true;
277 : }
278 :
279 i : *env_var_wstr = base::SysUTF8ToWide(env_var_str);
280 :
281 i : return true;
282 E : }
283 :
284 E : void AsanRuntime::PropagateFlagsValues() const {
285 : // TODO(sebmarchand): Look into edit-free ways to expose new flags to the
286 : // different modules.
287 E : HeapProxy::set_default_quarantine_max_size(flags_.quarantine_size);
288 E : StackCapture::set_bottom_frames_to_skip(flags_.bottom_frames_to_skip);
289 E : StackCaptureCache::set_compression_reporting_period(flags_.reporting_period);
290 E : stack_cache_->set_max_num_frames(flags_.max_num_frames);
291 E : }
292 :
293 E : void AsanRuntime::set_flags(const AsanFlags* flags) {
294 E : DCHECK(flags != NULL);
295 E : flags_ = *flags;
296 E : }
297 :
298 E : void AsanRuntime::AddHeap(HeapProxy* heap) {
299 E : base::AutoLock lock(heap_proxy_dlist_lock_);
300 E : InsertTailList(&heap_proxy_dlist_, HeapProxy::ToListEntry(heap));
301 E : }
302 :
303 E : void AsanRuntime::RemoveHeap(HeapProxy* heap) {
304 E : base::AutoLock lock(heap_proxy_dlist_lock_);
305 : DCHECK(HeapListContainsEntry(&heap_proxy_dlist_,
306 E : HeapProxy::ToListEntry(heap)));
307 E : RemoveEntryList(HeapProxy::ToListEntry(heap));
308 E : }
309 :
310 : void AsanRuntime::ReportAsanErrorDetails(const void* addr,
311 : const CONTEXT& context,
312 : const StackCapture& stack,
313 : HeapProxy::AccessMode access_mode,
314 E : size_t access_size) {
315 E : base::AutoLock lock(heap_proxy_dlist_lock_);
316 : // Iterates over the HeapProxy list to find the memory block containing this
317 : // address. We expect that there is at least one heap proxy extant.
318 E : HeapProxy* proxy = NULL;
319 E : LIST_ENTRY* item = heap_proxy_dlist_.Flink;
320 E : CHECK(item != NULL);
321 E : while (item != NULL) {
322 E : LIST_ENTRY* next_item = NULL;
323 E : if (item->Flink != &heap_proxy_dlist_) {
324 i : next_item = item->Flink;
325 : }
326 :
327 E : proxy = HeapProxy::FromListEntry(item);
328 E : if (proxy->OnBadAccess(addr, context, stack, access_mode, access_size)) {
329 E : break;
330 : }
331 :
332 i : item = next_item;
333 i : }
334 :
335 : // If item is NULL then we went through the list without finding the heap
336 : // from which this address was allocated. We can just reuse the logger of
337 : // the last heap proxy we saw to report an "unknown" error.
338 E : if (item == NULL) {
339 i : CHECK(proxy != NULL);
340 : proxy->ReportUnknownError(addr, context, stack, access_mode,
341 i : access_size);
342 : }
343 E : }
344 :
345 : } // namespace asan
346 : } // namespace agent
|