1 /* Replay a remote debug session logfile for GDB. 2 Copyright (C) 1996-2024 Free Software Foundation, Inc. 3 Written by Fred Fish (fnf@cygnus.com) from pieces of gdbserver. 4 5 This file is part of GDB. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 3 of the License, or 10 (at your option) any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 19 20 #undef PACKAGE 21 #undef PACKAGE_NAME 22 #undef PACKAGE_VERSION 23 #undef PACKAGE_STRING 24 #undef PACKAGE_TARNAME 25 26 #include <config.h> 27 #include "gdbsupport/version.h" 28 29 #if HAVE_SYS_FILE_H 30 #include <sys/file.h> 31 #endif 32 #if HAVE_SIGNAL_H 33 #include <signal.h> 34 #endif 35 #include <ctype.h> 36 #if HAVE_FCNTL_H 37 #include <fcntl.h> 38 #endif 39 #include <unistd.h> 40 #ifdef HAVE_NETINET_IN_H 41 #include <netinet/in.h> 42 #endif 43 #ifdef HAVE_SYS_SOCKET_H 44 #include <sys/socket.h> 45 #endif 46 #if HAVE_NETDB_H 47 #include <netdb.h> 48 #endif 49 #if HAVE_NETINET_TCP_H 50 #include <netinet/tcp.h> 51 #endif 52 53 #if USE_WIN32API 54 #include <ws2tcpip.h> 55 #endif 56 57 #include "gdbsupport/netstuff.h" 58 #include "gdbsupport/rsp-low.h" 59 60 #ifndef HAVE_SOCKLEN_T 61 typedef int socklen_t; 62 #endif 63 64 /* Sort of a hack... */ 65 #define EOL (EOF - 1) 66 67 static int remote_desc_in; 68 static int remote_desc_out; 69 70 static void 71 sync_error (FILE *fp, const char *desc, int expect, int got) 72 { 73 fprintf (stderr, "\n%s\n", desc); 74 fprintf (stderr, "At logfile offset %ld, expected '0x%x' got '0x%x'\n", 75 ftell (fp), expect, got); 76 fflush (stderr); 77 exit (1); 78 } 79 80 static void 81 remote_error (const char *desc) 82 { 83 fprintf (stderr, "\n%s\n", desc); 84 fflush (stderr); 85 exit (1); 86 } 87 88 static void 89 remote_close (void) 90 { 91 #ifdef USE_WIN32API 92 gdb_assert (remote_desc_in == remote_desc_out); 93 closesocket (remote_desc_in); 94 #else 95 close (remote_desc_in); 96 if (remote_desc_in != remote_desc_out) 97 close (remote_desc_out); 98 #endif 99 } 100 101 /* Open a connection to a remote debugger. 102 NAME is the filename used for communication. */ 103 104 static void 105 remote_open (const char *name) 106 { 107 #ifndef USE_WIN32API 108 if (strcmp (name, "-") == 0) 109 { 110 remote_desc_in = 0; 111 remote_desc_out = 1; 112 return; 113 } 114 #endif 115 116 const char *last_colon = strrchr (name, ':'); 117 118 if (last_colon == NULL) 119 { 120 fprintf (stderr, "%s: Must specify tcp connection as host:addr\n", name); 121 fflush (stderr); 122 exit (1); 123 } 124 125 #ifdef USE_WIN32API 126 static int winsock_initialized; 127 #endif 128 int tmp; 129 int tmp_desc; 130 struct addrinfo hint; 131 struct addrinfo *ainfo; 132 133 memset (&hint, 0, sizeof (hint)); 134 /* Assume no prefix will be passed, therefore we should use 135 AF_UNSPEC. */ 136 hint.ai_family = AF_UNSPEC; 137 hint.ai_socktype = SOCK_STREAM; 138 hint.ai_protocol = IPPROTO_TCP; 139 140 parsed_connection_spec parsed = parse_connection_spec (name, &hint); 141 142 if (parsed.port_str.empty ()) 143 error (_("Missing port on hostname '%s'"), name); 144 145 #ifdef USE_WIN32API 146 if (!winsock_initialized) 147 { 148 WSADATA wsad; 149 150 WSAStartup (MAKEWORD (1, 0), &wsad); 151 winsock_initialized = 1; 152 } 153 #endif 154 155 int r = getaddrinfo (parsed.host_str.c_str (), parsed.port_str.c_str (), 156 &hint, &ainfo); 157 158 if (r != 0) 159 { 160 fprintf (stderr, "%s:%s: cannot resolve name: %s\n", 161 parsed.host_str.c_str (), parsed.port_str.c_str (), 162 gai_strerror (r)); 163 fflush (stderr); 164 exit (1); 165 } 166 167 scoped_free_addrinfo free_ainfo (ainfo); 168 169 struct addrinfo *p; 170 171 for (p = ainfo; p != NULL; p = p->ai_next) 172 { 173 tmp_desc = socket (p->ai_family, p->ai_socktype, p->ai_protocol); 174 175 if (tmp_desc >= 0) 176 break; 177 } 178 179 if (p == NULL) 180 perror_with_name ("Cannot open socket"); 181 182 /* Allow rapid reuse of this port. */ 183 tmp = 1; 184 setsockopt (tmp_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, 185 sizeof (tmp)); 186 187 switch (p->ai_family) 188 { 189 case AF_INET: 190 ((struct sockaddr_in *) p->ai_addr)->sin_addr.s_addr = INADDR_ANY; 191 break; 192 case AF_INET6: 193 ((struct sockaddr_in6 *) p->ai_addr)->sin6_addr = in6addr_any; 194 break; 195 default: 196 fprintf (stderr, "Invalid 'ai_family' %d\n", p->ai_family); 197 exit (1); 198 } 199 200 if (bind (tmp_desc, p->ai_addr, p->ai_addrlen) != 0) 201 perror_with_name ("Can't bind address"); 202 203 if (p->ai_socktype == SOCK_DGRAM) 204 remote_desc_in = tmp_desc; 205 else 206 { 207 struct sockaddr_storage sockaddr; 208 socklen_t sockaddrsize = sizeof (sockaddr); 209 char orig_host[GDB_NI_MAX_ADDR], orig_port[GDB_NI_MAX_PORT]; 210 211 if (listen (tmp_desc, 1) != 0) 212 perror_with_name ("Can't listen on socket"); 213 214 remote_desc_in = accept (tmp_desc, (struct sockaddr *) &sockaddr, 215 &sockaddrsize); 216 217 if (remote_desc_in == -1) 218 perror_with_name ("Accept failed"); 219 220 /* Enable TCP keep alive process. */ 221 tmp = 1; 222 setsockopt (tmp_desc, SOL_SOCKET, SO_KEEPALIVE, 223 (char *) &tmp, sizeof (tmp)); 224 225 /* Tell TCP not to delay small packets. This greatly speeds up 226 interactive response. */ 227 tmp = 1; 228 setsockopt (remote_desc_in, IPPROTO_TCP, TCP_NODELAY, 229 (char *) &tmp, sizeof (tmp)); 230 231 if (getnameinfo ((struct sockaddr *) &sockaddr, sockaddrsize, 232 orig_host, sizeof (orig_host), 233 orig_port, sizeof (orig_port), 234 NI_NUMERICHOST | NI_NUMERICSERV) == 0) 235 { 236 fprintf (stderr, "Remote debugging from host %s, port %s\n", 237 orig_host, orig_port); 238 fflush (stderr); 239 } 240 241 #ifndef USE_WIN32API 242 close (tmp_desc); /* No longer need this */ 243 244 signal (SIGPIPE, SIG_IGN); /* If we don't do this, then 245 gdbreplay simply exits when 246 the remote side dies. */ 247 #else 248 closesocket (tmp_desc); /* No longer need this */ 249 #endif 250 } 251 252 #if defined(F_SETFL) && defined (FASYNC) 253 fcntl (remote_desc_in, F_SETFL, FASYNC); 254 #endif 255 remote_desc_out = remote_desc_in; 256 257 fprintf (stderr, "Replay logfile using %s\n", name); 258 fflush (stderr); 259 } 260 261 static int 262 logchar (FILE *fp) 263 { 264 int ch; 265 int ch2; 266 267 ch = fgetc (fp); 268 if (ch != '\r') 269 { 270 fputc (ch, stderr); 271 fflush (stderr); 272 } 273 switch (ch) 274 { 275 /* Treat \r\n as a newline. */ 276 case '\r': 277 ch = fgetc (fp); 278 if (ch == '\n') 279 ch = EOL; 280 else 281 { 282 ungetc (ch, fp); 283 ch = '\r'; 284 } 285 fputc (ch == EOL ? '\n' : '\r', stderr); 286 fflush (stderr); 287 break; 288 case '\n': 289 ch = EOL; 290 break; 291 case '\\': 292 ch = fgetc (fp); 293 fputc (ch, stderr); 294 fflush (stderr); 295 switch (ch) 296 { 297 case '\\': 298 break; 299 case 'b': 300 ch = '\b'; 301 break; 302 case 'f': 303 ch = '\f'; 304 break; 305 case 'n': 306 ch = '\n'; 307 break; 308 case 'r': 309 ch = '\r'; 310 break; 311 case 't': 312 ch = '\t'; 313 break; 314 case 'v': 315 ch = '\v'; 316 break; 317 case 'x': 318 ch2 = fgetc (fp); 319 fputc (ch2, stderr); 320 fflush (stderr); 321 ch = fromhex (ch2) << 4; 322 ch2 = fgetc (fp); 323 fputc (ch2, stderr); 324 fflush (stderr); 325 ch |= fromhex (ch2); 326 break; 327 default: 328 /* Treat any other char as just itself */ 329 break; 330 } 331 default: 332 break; 333 } 334 return (ch); 335 } 336 337 static int 338 gdbchar (int desc) 339 { 340 unsigned char fromgdb; 341 342 if (read (desc, &fromgdb, 1) != 1) 343 return -1; 344 else 345 return fromgdb; 346 } 347 348 /* Accept input from gdb and match with chars from fp (after skipping one 349 blank) up until a \n is read from fp (which is not matched) */ 350 351 static void 352 expect (FILE *fp) 353 { 354 int fromlog; 355 int fromgdb; 356 357 if ((fromlog = logchar (fp)) != ' ') 358 { 359 sync_error (fp, "Sync error during gdb read of leading blank", ' ', 360 fromlog); 361 } 362 do 363 { 364 fromlog = logchar (fp); 365 if (fromlog == EOL) 366 break; 367 fromgdb = gdbchar (remote_desc_in); 368 if (fromgdb < 0) 369 remote_error ("Error during read from gdb"); 370 } 371 while (fromlog == fromgdb); 372 373 if (fromlog != EOL) 374 { 375 sync_error (fp, "Sync error during read of gdb packet from log", fromlog, 376 fromgdb); 377 } 378 } 379 380 /* Play data back to gdb from fp (after skipping leading blank) up until a 381 \n is read from fp (which is discarded and not sent to gdb). */ 382 383 static void 384 play (FILE *fp) 385 { 386 int fromlog; 387 char ch; 388 389 if ((fromlog = logchar (fp)) != ' ') 390 { 391 sync_error (fp, "Sync error skipping blank during write to gdb", ' ', 392 fromlog); 393 } 394 while ((fromlog = logchar (fp)) != EOL) 395 { 396 ch = fromlog; 397 if (write (remote_desc_out, &ch, 1) != 1) 398 remote_error ("Error during write to gdb"); 399 } 400 } 401 402 static void 403 gdbreplay_version (void) 404 { 405 printf ("GNU gdbreplay %s%s\n" 406 "Copyright (C) 2024 Free Software Foundation, Inc.\n" 407 "gdbreplay is free software, covered by " 408 "the GNU General Public License.\n" 409 "This gdbreplay was configured as \"%s\"\n", 410 PKGVERSION, version, host_name); 411 } 412 413 static void 414 gdbreplay_usage (FILE *stream) 415 { 416 fprintf (stream, "Usage:\tgdbreplay LOGFILE HOST:PORT\n"); 417 if (REPORT_BUGS_TO[0] && stream == stdout) 418 fprintf (stream, "Report bugs to \"%s\".\n", REPORT_BUGS_TO); 419 } 420 421 /* Main function. This is called by the real "main" function, 422 wrapped in a TRY_CATCH that handles any uncaught exceptions. */ 423 424 static void ATTRIBUTE_NORETURN 425 captured_main (int argc, char *argv[]) 426 { 427 FILE *fp; 428 int ch; 429 430 if (argc >= 2 && strcmp (argv[1], "--version") == 0) 431 { 432 gdbreplay_version (); 433 exit (0); 434 } 435 if (argc >= 2 && strcmp (argv[1], "--help") == 0) 436 { 437 gdbreplay_usage (stdout); 438 exit (0); 439 } 440 441 if (argc < 3) 442 { 443 gdbreplay_usage (stderr); 444 exit (1); 445 } 446 fp = fopen (argv[1], "r"); 447 if (fp == NULL) 448 { 449 perror_with_name (argv[1]); 450 } 451 remote_open (argv[2]); 452 while ((ch = logchar (fp)) != EOF) 453 { 454 switch (ch) 455 { 456 case 'w': 457 /* data sent from gdb to gdbreplay, accept and match it */ 458 expect (fp); 459 break; 460 case 'r': 461 /* data sent from gdbreplay to gdb, play it */ 462 play (fp); 463 break; 464 case 'c': 465 /* Command executed by gdb */ 466 while ((ch = logchar (fp)) != EOL); 467 break; 468 } 469 } 470 remote_close (); 471 exit (0); 472 } 473 474 int 475 main (int argc, char *argv[]) 476 { 477 try 478 { 479 captured_main (argc, argv); 480 } 481 catch (const gdb_exception &exception) 482 { 483 if (exception.reason == RETURN_ERROR) 484 { 485 fflush (stdout); 486 fprintf (stderr, "%s\n", exception.what ()); 487 } 488 489 exit (1); 490 } 491 492 gdb_assert_not_reached ("captured_main should never return"); 493 } 494