OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/debug/stack_trace.h" | 5 #include "base/debug/stack_trace.h" |
6 | 6 |
7 #include <errno.h> | 7 #include <errno.h> |
8 #include <execinfo.h> | 8 #include <execinfo.h> |
9 #include <fcntl.h> | 9 #include <fcntl.h> |
10 #include <signal.h> | 10 #include <signal.h> |
11 #include <stdio.h> | 11 #include <stdio.h> |
12 #include <stdlib.h> | 12 #include <stdlib.h> |
13 #include <sys/param.h> | 13 #include <sys/param.h> |
14 #include <sys/stat.h> | 14 #include <sys/stat.h> |
15 #include <sys/types.h> | 15 #include <sys/types.h> |
16 #include <unistd.h> | 16 #include <unistd.h> |
17 | 17 |
18 #include <string> | 18 #include <ostream> |
19 #include <vector> | |
20 | 19 |
21 #if defined(__GLIBCXX__) | 20 #if defined(__GLIBCXX__) |
22 #include <cxxabi.h> | 21 #include <cxxabi.h> |
23 #endif | 22 #endif |
24 | 23 |
25 #if defined(OS_MACOSX) | 24 #if defined(OS_MACOSX) |
26 #include <AvailabilityMacros.h> | 25 #include <AvailabilityMacros.h> |
27 #endif | 26 #endif |
28 | 27 |
29 #include "base/basictypes.h" | 28 #include "base/basictypes.h" |
| 29 #include "base/debug/debugger.h" |
30 #include "base/eintr_wrapper.h" | 30 #include "base/eintr_wrapper.h" |
31 #include "base/logging.h" | 31 #include "base/logging.h" |
32 #include "base/memory/scoped_ptr.h" | 32 #include "base/memory/scoped_ptr.h" |
33 #include "base/safe_strerror_posix.h" | 33 #include "base/string_number_conversions.h" |
34 #include "base/string_piece.h" | |
35 #include "base/stringprintf.h" | |
36 | 34 |
37 #if defined(USE_SYMBOLIZE) | 35 #if defined(USE_SYMBOLIZE) |
38 #include "base/third_party/symbolize/symbolize.h" | 36 #include "base/third_party/symbolize/symbolize.h" |
39 #endif | 37 #endif |
40 | 38 |
41 namespace base { | 39 namespace base { |
42 namespace debug { | 40 namespace debug { |
43 | 41 |
44 namespace { | 42 namespace { |
45 | 43 |
| 44 volatile sig_atomic_t in_signal_handler = 0; |
| 45 |
46 // The prefix used for mangled symbols, per the Itanium C++ ABI: | 46 // The prefix used for mangled symbols, per the Itanium C++ ABI: |
47 // http://www.codesourcery.com/cxx-abi/abi.html#mangling | 47 // http://www.codesourcery.com/cxx-abi/abi.html#mangling |
48 const char kMangledSymbolPrefix[] = "_Z"; | 48 const char kMangledSymbolPrefix[] = "_Z"; |
49 | 49 |
50 // Characters that can be used for symbols, generated by Ruby: | 50 // Characters that can be used for symbols, generated by Ruby: |
51 // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join | 51 // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join |
52 const char kSymbolCharacters[] = | 52 const char kSymbolCharacters[] = |
53 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; | 53 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; |
54 | 54 |
55 #if !defined(USE_SYMBOLIZE) | 55 #if !defined(USE_SYMBOLIZE) |
56 // Demangles C++ symbols in the given text. Example: | 56 // Demangles C++ symbols in the given text. Example: |
57 // | 57 // |
58 // "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" | 58 // "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" |
59 // => | 59 // => |
60 // "out/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]" | 60 // "out/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]" |
61 void DemangleSymbols(std::string* text) { | 61 void DemangleSymbols(std::string* text) { |
| 62 // Note: code in this function is NOT async-signal safe (std::string uses |
| 63 // malloc internally). |
| 64 |
62 #if defined(__GLIBCXX__) | 65 #if defined(__GLIBCXX__) |
63 | 66 |
64 std::string::size_type search_from = 0; | 67 std::string::size_type search_from = 0; |
65 while (search_from < text->size()) { | 68 while (search_from < text->size()) { |
66 // Look for the start of a mangled symbol, from search_from. | 69 // Look for the start of a mangled symbol, from search_from. |
67 std::string::size_type mangled_start = | 70 std::string::size_type mangled_start = |
68 text->find(kMangledSymbolPrefix, search_from); | 71 text->find(kMangledSymbolPrefix, search_from); |
69 if (mangled_start == std::string::npos) { | 72 if (mangled_start == std::string::npos) { |
70 break; // Mangled symbol not found. | 73 break; // Mangled symbol not found. |
71 } | 74 } |
(...skipping 21 matching lines...) Expand all Loading... |
93 } else { | 96 } else { |
94 // Failed to demangle. Retry after the "_Z" we just found. | 97 // Failed to demangle. Retry after the "_Z" we just found. |
95 search_from = mangled_start + 2; | 98 search_from = mangled_start + 2; |
96 } | 99 } |
97 } | 100 } |
98 | 101 |
99 #endif // defined(__GLIBCXX__) | 102 #endif // defined(__GLIBCXX__) |
100 } | 103 } |
101 #endif // !defined(USE_SYMBOLIZE) | 104 #endif // !defined(USE_SYMBOLIZE) |
102 | 105 |
103 // Gets the backtrace as a vector of strings. If possible, resolve symbol | 106 class BacktraceOutputHandler { |
104 // names and attach these. Otherwise just use raw addresses. Returns true | 107 public: |
105 // if any symbol name is resolved. Returns false on error and *may* fill | 108 virtual void HandleOutput(const char* output) = 0; |
106 // in |error_message| if an error message is available. | 109 |
107 bool GetBacktraceStrings(void *const *trace, int size, | 110 protected: |
108 std::vector<std::string>* trace_strings, | 111 virtual ~BacktraceOutputHandler() {} |
109 std::string* error_message) { | 112 }; |
110 bool symbolized = false; | 113 |
| 114 void OutputPointer(void* pointer, BacktraceOutputHandler* handler) { |
| 115 char buf[1024] = { '\0' }; |
| 116 handler->HandleOutput(" [0x"); |
| 117 internal::itoa_r(reinterpret_cast<intptr_t>(pointer), buf, sizeof(buf), 16); |
| 118 handler->HandleOutput(buf); |
| 119 handler->HandleOutput("]"); |
| 120 } |
| 121 |
| 122 void ProcessBacktrace(void *const *trace, |
| 123 int size, |
| 124 BacktraceOutputHandler* handler) { |
| 125 // NOTE: This code MUST be async-signal safe (it's used by in-process |
| 126 // stack dumping signal handler). NO malloc or stdio is allowed here. |
111 | 127 |
112 #if defined(USE_SYMBOLIZE) | 128 #if defined(USE_SYMBOLIZE) |
113 for (int i = 0; i < size; ++i) { | 129 for (int i = 0; i < size; ++i) { |
114 char symbol[1024]; | 130 handler->HandleOutput("\t"); |
| 131 |
| 132 char buf[1024] = { '\0' }; |
| 133 |
115 // Subtract by one as return address of function may be in the next | 134 // Subtract by one as return address of function may be in the next |
116 // function when a function is annotated as noreturn. | 135 // function when a function is annotated as noreturn. |
117 if (google::Symbolize(static_cast<char *>(trace[i]) - 1, | 136 void* address = static_cast<char*>(trace[i]) - 1; |
118 symbol, sizeof(symbol))) { | 137 if (google::Symbolize(address, buf, sizeof(buf))) |
119 // Don't call DemangleSymbols() here as the symbol is demangled by | 138 handler->HandleOutput(buf); |
120 // google::Symbolize(). | 139 else |
121 trace_strings->push_back( | 140 handler->HandleOutput("<unknown>"); |
122 base::StringPrintf("%s [%p]", symbol, trace[i])); | 141 |
123 symbolized = true; | 142 OutputPointer(trace[i], handler); |
124 } else { | 143 handler->HandleOutput("\n"); |
125 trace_strings->push_back(base::StringPrintf("%p", trace[i])); | 144 } |
| 145 #else |
| 146 bool printed = false; |
| 147 |
| 148 // Below part is async-signal unsafe (uses malloc), so execute it only |
| 149 // when we are not executing the signal handler. |
| 150 if (in_signal_handler == 0) { |
| 151 scoped_ptr_malloc<char*> trace_symbols(backtrace_symbols(trace, size)); |
| 152 if (trace_symbols.get()) { |
| 153 for (int i = 0; i < size; ++i) { |
| 154 std::string trace_symbol = trace_symbols.get()[i]; |
| 155 DemangleSymbols(&trace_symbol); |
| 156 handler->HandleOutput(trace_symbol.c_str()); |
| 157 handler->HandleOutput("\n"); |
| 158 } |
| 159 |
| 160 printed = true; |
126 } | 161 } |
127 } | 162 } |
128 #else | 163 |
129 scoped_ptr_malloc<char*> trace_symbols(backtrace_symbols(trace, size)); | 164 if (!printed) { |
130 if (trace_symbols.get()) { | |
131 for (int i = 0; i < size; ++i) { | 165 for (int i = 0; i < size; ++i) { |
132 std::string trace_symbol = trace_symbols.get()[i]; | 166 OutputPointer(trace[i], handler); |
133 DemangleSymbols(&trace_symbol); | 167 handler->HandleOutput("\n"); |
134 trace_strings->push_back(trace_symbol); | |
135 } | |
136 symbolized = true; | |
137 } else { | |
138 if (error_message) | |
139 *error_message = safe_strerror(errno); | |
140 for (int i = 0; i < size; ++i) { | |
141 trace_strings->push_back(base::StringPrintf("%p", trace[i])); | |
142 } | 168 } |
143 } | 169 } |
144 #endif // defined(USE_SYMBOLIZE) | 170 #endif // defined(USE_SYMBOLIZE) |
145 | |
146 return symbolized; | |
147 } | 171 } |
148 | 172 |
149 void StackDumpSignalHandler(int signal, siginfo_t* info, ucontext_t* context) { | 173 void StackDumpSignalHandler(int signal, siginfo_t* info, ucontext_t* context) { |
| 174 // NOTE: This code MUST be async-signal safe. |
| 175 // NO malloc or stdio is allowed here. |
| 176 |
| 177 // Record the fact that we are in the signal handler now, so that the rest |
| 178 // of StackTrace can behave in an async-signal-safe manner. |
| 179 in_signal_handler = 1; |
| 180 |
150 if (BeingDebugged()) | 181 if (BeingDebugged()) |
151 BreakDebugger(); | 182 BreakDebugger(); |
152 | 183 |
153 #if defined(OS_MACOSX) | 184 char buf[1024] = "Received signal "; |
154 // TODO(phajdan.jr): Fix async-signal non-safety (http://crbug.com/101155). | 185 size_t buf_len = strlen(buf); |
155 DLOG(ERROR) << "Received signal " << signal; | 186 internal::itoa_r(signal, buf + buf_len, sizeof(buf) - buf_len, 10); |
156 StackTrace().PrintBacktrace(); | 187 RAW_LOG(ERROR, buf); |
157 #endif | 188 |
| 189 debug::StackTrace().PrintBacktrace(); |
158 | 190 |
159 // TODO(shess): Port to Linux. | 191 // TODO(shess): Port to Linux. |
160 #if defined(OS_MACOSX) | 192 #if defined(OS_MACOSX) |
161 // TODO(shess): Port to 64-bit. | 193 // TODO(shess): Port to 64-bit. |
162 #if ARCH_CPU_X86_FAMILY && ARCH_CPU_32_BITS | 194 #if ARCH_CPU_X86_FAMILY && ARCH_CPU_32_BITS |
163 char buf[1024]; | |
164 size_t len; | 195 size_t len; |
165 | 196 |
166 // NOTE: Even |snprintf()| is not on the approved list for signal | 197 // NOTE: Even |snprintf()| is not on the approved list for signal |
167 // handlers, but buffered I/O is definitely not on the list due to | 198 // handlers, but buffered I/O is definitely not on the list due to |
168 // potential for |malloc()|. | 199 // potential for |malloc()|. |
169 len = static_cast<size_t>( | 200 len = static_cast<size_t>( |
170 snprintf(buf, sizeof(buf), | 201 snprintf(buf, sizeof(buf), |
171 "ax: %x, bx: %x, cx: %x, dx: %x\n", | 202 "ax: %x, bx: %x, cx: %x, dx: %x\n", |
172 context->uc_mcontext->__ss.__eax, | 203 context->uc_mcontext->__ss.__eax, |
173 context->uc_mcontext->__ss.__ebx, | 204 context->uc_mcontext->__ss.__ebx, |
(...skipping 20 matching lines...) Expand all Loading... |
194 context->uc_mcontext->__ss.__ds, | 225 context->uc_mcontext->__ss.__ds, |
195 context->uc_mcontext->__ss.__es, | 226 context->uc_mcontext->__ss.__es, |
196 context->uc_mcontext->__ss.__fs, | 227 context->uc_mcontext->__ss.__fs, |
197 context->uc_mcontext->__ss.__gs)); | 228 context->uc_mcontext->__ss.__gs)); |
198 write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); | 229 write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); |
199 #endif // ARCH_CPU_32_BITS | 230 #endif // ARCH_CPU_32_BITS |
200 #endif // defined(OS_MACOSX) | 231 #endif // defined(OS_MACOSX) |
201 _exit(1); | 232 _exit(1); |
202 } | 233 } |
203 | 234 |
| 235 class PrintBacktraceOutputHandler : public BacktraceOutputHandler { |
| 236 public: |
| 237 PrintBacktraceOutputHandler() {} |
| 238 |
| 239 virtual void HandleOutput(const char* output) { |
| 240 // NOTE: This code MUST be async-signal safe (it's used by in-process |
| 241 // stack dumping signal handler). NO malloc or stdio is allowed here. |
| 242 ignore_result(HANDLE_EINTR(write(STDERR_FILENO, output, strlen(output)))); |
| 243 } |
| 244 |
| 245 private: |
| 246 DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler); |
| 247 }; |
| 248 |
| 249 class StreamBacktraceOutputHandler : public BacktraceOutputHandler { |
| 250 public: |
| 251 StreamBacktraceOutputHandler(std::ostream* os) : os_(os) { |
| 252 } |
| 253 |
| 254 virtual void HandleOutput(const char* output) { |
| 255 (*os_) << output; |
| 256 } |
| 257 |
| 258 private: |
| 259 std::ostream* os_; |
| 260 |
| 261 DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler); |
| 262 }; |
| 263 |
| 264 void WarmUpBacktrace() { |
| 265 // Warm up stack trace infrastructure. It turns out that on the first |
| 266 // call glibc initializes some internal data structures using pthread_once, |
| 267 // and even backtrace() can call malloc(), leading to hangs. |
| 268 // |
| 269 // Example stack trace snippet (with tcmalloc): |
| 270 // |
| 271 // #8 0x0000000000a173b5 in tc_malloc |
| 272 // at ./third_party/tcmalloc/chromium/src/debugallocation.cc:1161 |
| 273 // #9 0x00007ffff7de7900 in _dl_map_object_deps at dl-deps.c:517 |
| 274 // #10 0x00007ffff7ded8a9 in dl_open_worker at dl-open.c:262 |
| 275 // #11 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178 |
| 276 // #12 0x00007ffff7ded31a in _dl_open (file=0x7ffff625e298 "libgcc_s.so.1") |
| 277 // at dl-open.c:639 |
| 278 // #13 0x00007ffff6215602 in do_dlopen at dl-libc.c:89 |
| 279 // #14 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178 |
| 280 // #15 0x00007ffff62156c4 in dlerror_run at dl-libc.c:48 |
| 281 // #16 __GI___libc_dlopen_mode at dl-libc.c:165 |
| 282 // #17 0x00007ffff61ef8f5 in init |
| 283 // at ../sysdeps/x86_64/../ia64/backtrace.c:53 |
| 284 // #18 0x00007ffff6aad400 in pthread_once |
| 285 // at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S:104 |
| 286 // #19 0x00007ffff61efa14 in __GI___backtrace |
| 287 // at ../sysdeps/x86_64/../ia64/backtrace.c:104 |
| 288 // #20 0x0000000000752a54 in base::debug::StackTrace::StackTrace |
| 289 // at base/debug/stack_trace_posix.cc:175 |
| 290 // #21 0x00000000007a4ae5 in |
| 291 // base::(anonymous namespace)::StackDumpSignalHandler |
| 292 // at base/process_util_posix.cc:172 |
| 293 // #22 <signal handler called> |
| 294 StackTrace stack_trace; |
| 295 } |
| 296 |
204 } // namespace | 297 } // namespace |
205 | 298 |
206 #if !defined(OS_IOS) | 299 #if !defined(OS_IOS) |
207 bool EnableInProcessStackDumping() { | 300 bool EnableInProcessStackDumping() { |
208 // When running in an application, our code typically expects SIGPIPE | 301 // When running in an application, our code typically expects SIGPIPE |
209 // to be ignored. Therefore, when testing that same code, it should run | 302 // to be ignored. Therefore, when testing that same code, it should run |
210 // with SIGPIPE ignored as well. | 303 // with SIGPIPE ignored as well. |
211 struct sigaction action; | 304 struct sigaction action; |
212 memset(&action, 0, sizeof(action)); | 305 memset(&action, 0, sizeof(action)); |
213 action.sa_handler = SIG_IGN; | 306 action.sa_handler = SIG_IGN; |
214 sigemptyset(&action.sa_mask); | 307 sigemptyset(&action.sa_mask); |
215 bool success = (sigaction(SIGPIPE, &action, NULL) == 0); | 308 bool success = (sigaction(SIGPIPE, &action, NULL) == 0); |
216 | 309 |
| 310 // Avoid hangs during backtrace initialization, see above. |
| 311 WarmUpBacktrace(); |
| 312 |
217 sig_t handler = reinterpret_cast<sig_t>(&StackDumpSignalHandler); | 313 sig_t handler = reinterpret_cast<sig_t>(&StackDumpSignalHandler); |
218 success &= (signal(SIGILL, handler) != SIG_ERR); | 314 success &= (signal(SIGILL, handler) != SIG_ERR); |
219 success &= (signal(SIGABRT, handler) != SIG_ERR); | 315 success &= (signal(SIGABRT, handler) != SIG_ERR); |
220 success &= (signal(SIGFPE, handler) != SIG_ERR); | 316 success &= (signal(SIGFPE, handler) != SIG_ERR); |
221 success &= (signal(SIGBUS, handler) != SIG_ERR); | 317 success &= (signal(SIGBUS, handler) != SIG_ERR); |
222 success &= (signal(SIGSEGV, handler) != SIG_ERR); | 318 success &= (signal(SIGSEGV, handler) != SIG_ERR); |
223 success &= (signal(SIGSYS, handler) != SIG_ERR); | 319 success &= (signal(SIGSYS, handler) != SIG_ERR); |
224 | 320 |
225 return success; | 321 return success; |
226 } | 322 } |
227 #endif // !defined(OS_IOS) | 323 #endif // !defined(OS_IOS) |
228 | 324 |
229 StackTrace::StackTrace() { | 325 StackTrace::StackTrace() { |
| 326 // NOTE: This code MUST be async-signal safe (it's used by in-process |
| 327 // stack dumping signal handler). NO malloc or stdio is allowed here. |
| 328 |
230 // Though the backtrace API man page does not list any possible negative | 329 // Though the backtrace API man page does not list any possible negative |
231 // return values, we take no chance. | 330 // return values, we take no chance. |
232 count_ = std::max(backtrace(trace_, arraysize(trace_)), 0); | 331 count_ = std::max(backtrace(trace_, arraysize(trace_)), 0); |
233 } | 332 } |
234 | 333 |
235 void StackTrace::PrintBacktrace() const { | 334 void StackTrace::PrintBacktrace() const { |
236 fflush(stderr); | 335 // NOTE: This code MUST be async-signal safe (it's used by in-process |
237 std::vector<std::string> trace_strings; | 336 // stack dumping signal handler). NO malloc or stdio is allowed here. |
238 GetBacktraceStrings(trace_, count_, &trace_strings, NULL); | 337 |
239 for (size_t i = 0; i < trace_strings.size(); ++i) { | 338 PrintBacktraceOutputHandler handler; |
240 fprintf(stderr, "\t%s\n", trace_strings[i].c_str()); | 339 ProcessBacktrace(trace_, count_, &handler); |
241 } | |
242 } | 340 } |
243 | 341 |
244 void StackTrace::OutputToStream(std::ostream* os) const { | 342 void StackTrace::OutputToStream(std::ostream* os) const { |
245 std::vector<std::string> trace_strings; | 343 StreamBacktraceOutputHandler handler(os); |
246 std::string error_message; | 344 ProcessBacktrace(trace_, count_, &handler); |
247 if (GetBacktraceStrings(trace_, count_, &trace_strings, &error_message)) { | 345 } |
248 (*os) << "Backtrace:\n"; | 346 |
249 } else { | 347 namespace internal { |
250 if (!error_message.empty()) | 348 |
251 error_message = " (" + error_message + ")"; | 349 // NOTE: code from sandbox/linux/seccomp-bpf/demo.cc. |
252 (*os) << "Unable to get symbols for backtrace" << error_message << ". " | 350 char *itoa_r(intptr_t i, char *buf, size_t sz, int base) { |
253 << "Dumping raw addresses in trace:\n"; | 351 // Make sure we can write at least one NUL byte. |
| 352 size_t n = 1; |
| 353 if (n > sz) |
| 354 return NULL; |
| 355 |
| 356 if (base < 2 || base > 16) { |
| 357 buf[0] = '\000'; |
| 358 return NULL; |
254 } | 359 } |
255 | 360 |
256 for (size_t i = 0; i < trace_strings.size(); ++i) { | 361 char *start = buf; |
257 (*os) << "\t" << trace_strings[i] << "\n"; | 362 |
| 363 uintptr_t j = i; |
| 364 |
| 365 // Handle negative numbers (only for base 10). |
| 366 if (i < 0 && base == 10) { |
| 367 j = -i; |
| 368 |
| 369 // Make sure we can write the '-' character. |
| 370 if (++n > sz) { |
| 371 buf[0] = '\000'; |
| 372 return NULL; |
| 373 } |
| 374 *start++ = '-'; |
258 } | 375 } |
| 376 |
| 377 // Loop until we have converted the entire number. Output at least one |
| 378 // character (i.e. '0'). |
| 379 char *ptr = start; |
| 380 do { |
| 381 // Make sure there is still enough space left in our output buffer. |
| 382 if (++n > sz) { |
| 383 buf[0] = '\000'; |
| 384 return NULL; |
| 385 } |
| 386 |
| 387 // Output the next digit. |
| 388 *ptr++ = "0123456789abcdef"[j % base]; |
| 389 j /= base; |
| 390 } while (j); |
| 391 |
| 392 // Terminate the output with a NUL character. |
| 393 *ptr = '\000'; |
| 394 |
| 395 // Conversion to ASCII actually resulted in the digits being in reverse |
| 396 // order. We can't easily generate them in forward order, as we can't tell |
| 397 // the number of characters needed until we are done converting. |
| 398 // So, now, we reverse the string (except for the possible "-" sign). |
| 399 while (--ptr > start) { |
| 400 char ch = *ptr; |
| 401 *ptr = *start; |
| 402 *start++ = ch; |
| 403 } |
| 404 return buf; |
259 } | 405 } |
260 | 406 |
| 407 } // namespace internal |
| 408 |
261 } // namespace debug | 409 } // namespace debug |
262 } // namespace base | 410 } // namespace base |
OLD | NEW |