winedbg: function-local displays

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Well, I know that everyone should use winedbg --gdb, but I still find
debugging with plain winedbg easier. So there's a patch to improve it a bit
more.

ChangeLog:
  - Extend displays: now one can add displays active only in specified
    function; displays now can be enabled/disabled like in gdb

--- programs/winedbg/debug.l~	2003-09-19 01:04:24.000000000 +0200
+++ programs/winedbg/debug.l	2003-09-19 06:37:06.000000000 +0200
@@ -57,7 +57,8 @@
 %s PATH_EXPECTED
 %s INFO_CMD
 %s HELP_CMD
-%s DEL_CMD
+%s BD_CMD
+%s LOCAL_CMD
 %s WALK_CMD
 %s SHOW_CMD
 %s MODE_CMD
@@ -105,12 +106,14 @@
 <INITIAL>down|dow|do			{ BEGIN(NOCMD); return tDOWN; }
 <INITIAL>frame|fram|fra|fr		{ BEGIN(NOCMD); return tFRAME; }
 <INITIAL>list|lis|li|l			{ BEGIN(PATH_EXPECTED); return tLIST; }
-<INITIAL>enable|enabl|enab|ena		{ BEGIN(NOCMD); return tENABLE;}
-<INITIAL>disable|disabl|disab|disa|dis  { BEGIN(NOCMD); return tDISABLE; }
+<INITIAL>enable|enabl|enab|ena		{ BEGIN(BD_CMD); return tENABLE;}
+<INITIAL>disable|disabl|disab|disa|dis  { BEGIN(BD_CMD); return tDISABLE; }
 <INITIAL>disassemble|disassembl|disassemb|disassem|disasse|disass|disas { BEGIN(NOCMD); return tDISASSEMBLE; }
-<INITIAL,INFO_CMD,DEL_CMD>display|displa|displ|disp	{ BEGIN(FORMAT_EXPECTED); return tDISPLAY; }
+<INITIAL>locally|local			{ BEGIN(LOCAL_CMD); return tLOCAL; }
+<INITIAL,LOCAL_CMD>display|displa|displ|disp	{ BEGIN(FORMAT_EXPECTED); return tDISPLAY; }
+<INFO_CMD,BD_CMD>display|displa|displ|disp|dis|di|d	{ BEGIN(NOCMD); return tDISPLAY; }
 <INITIAL>undisplay|undispla|undispl|undisp|undis|undi|und	{ BEGIN(NOCMD); return tUNDISPLAY; }
-<INITIAL>delete|delet|dele|del		{ BEGIN(DEL_CMD); return tDELETE; }
+<INITIAL>delete|delet|dele|del		{ BEGIN(BD_CMD); return tDELETE; }
 <INITIAL,NOPROCESS>quit|qui|qu|q	{ BEGIN(NOCMD); return tQUIT; }
 <INITIAL>set|se				{ BEGIN(NOCMD); return tSET; }
 <INITIAL,NOPROCESS>walk|w		{ BEGIN(WALK_CMD); return tWALK; }
@@ -137,7 +140,7 @@
 <INITIAL,NOPROCESS>source|sourc|sour|src { BEGIN(PATH_EXPECTED); return tSOURCE; }
 <INITIAL>symbolfile|symbols|symbol|sf   { BEGIN(PATH_EXPECTED); return tSYMBOLFILE; }
 
-<INITIAL,INFO_CMD,DEL_CMD>break|brea|bre|br|b	{ BEGIN(NOCMD); return tBREAK; }
+<INITIAL,INFO_CMD,BD_CMD>break|brea|bre|br|b	{ BEGIN(NOCMD); return tBREAK; }
 <INITIAL>watch|watc|wat			{ BEGIN(NOCMD); return tWATCH; }
 <INITIAL>whatis|whati|what		{ BEGIN(NOCMD); return tWHATIS; }
 <INITIAL,NOPROCESS>run|ru|r     	{ BEGIN(ASTRING_EXPECTED); return tRUN;}
--- programs/winedbg/debugger.h~	2003-09-19 05:47:19.000000000 +0200
+++ programs/winedbg/debugger.h	2003-09-19 06:37:06.000000000 +0200
@@ -330,10 +330,11 @@
 extern int  DEBUG_ReadLine(const char* pfx, char* buffer, int size);
 
   /* debugger/display.c */
-extern int DEBUG_DoDisplay(void);
-extern int DEBUG_AddDisplay(struct expr * exp, int count, char format);
-extern int DEBUG_DelDisplay(int displaynum);
-extern int DEBUG_InfoDisplay(void);
+int DEBUG_DoDisplay(void);
+int DEBUG_AddDisplay(struct expr * exp, int count, char format, int local_frame);
+int DEBUG_DelDisplay(int displaynum);
+int DEBUG_InfoDisplay(void);
+int DEBUG_EnableDisplay(int displaynum, int enable);
 
   /* debugger/expr.c */
 extern void DEBUG_FreeExprMem(void);
@@ -389,6 +390,7 @@
 				    struct datatype * type);
 extern BOOL DEBUG_Normalize(struct name_hash * nh );
 void DEBUG_InfoSymbols(const char* str);
+const char *DEBUG_GetSymbolName(const struct name_hash *);
 
   /* debugger/info.c */
 extern void DEBUG_PrintBasic( const DBG_VALUE* value, int count, char format );
--- programs/winedbg/hash.c~	2003-09-19 06:27:27.000000000 +0200
+++ programs/winedbg/hash.c	2003-09-19 06:37:06.000000000 +0200
@@ -1328,6 +1328,11 @@
   return TRUE;
 }
 
+const char *DEBUG_GetSymbolName(const struct name_hash * sym)
+{
+	return sym->name;
+}
+
 #ifdef HAVE_REGEX_H
 
 static int cmp_sym_by_name(const void * p1, const void * p2)
--- programs/winedbg/info.c~	2003-09-19 01:04:24.000000000 +0200
+++ programs/winedbg/info.c	2003-09-19 06:37:06.000000000 +0200
@@ -218,15 +218,16 @@
 "  stepi [N]                              nexti [N]",
 "  x <addr>                               print <expr>",
 "  display <expr>                         undisplay <disnum>",
-"  delete display <disnum>                pass",
+"  local display <expr>                   delete display <disnum>",                  
+"  enable display <disnum>                disable display <disnum>",
 "  bt                                     frame <n>",
 "  up                                     down",
 "  list <lines>                           disassemble [<addr>][,<addr>]",
 "  show dir                               dir <path>",
 "  set <reg> = <expr>                     set *<addr> = <expr>",
-"  mode [16,32,vm86]                      walk [wnd,class,module,maps,",
-"  whatis                                       process,thread,exception]",
-"  info (see 'help info' for options)",
+"  mode [16,32,vm86]                      pass",
+"  whatis                                 walk [wnd,class,module,maps,",
+"  info (see 'help info' for options)           process,thread,exception]",
 
 "The 'x' command accepts repeat counts and formats (including 'i') in the",
 "same way that gdb does.\n",
@@ -234,8 +235,8 @@
 " The following are examples of legal expressions:",
 " $eax     $eax+0x3   0x1000   ($eip + 256)  *$eax   *($esp + 3)",
 " Also, a nm format symbol table can be read from a file using the",
-" symbolfile command.  Symbols can also be defined individually with",
-" the define command.",
+" symbolfile command.", /*  Symbols can also be defined individually with",
+" the define command.", */
 "",
 NULL
 };
Index: programs/winedbg/dbg.y
===================================================================
RCS file: /home/wine/wine/programs/winedbg/dbg.y,v
retrieving revision 1.9
diff -u -r1.9 dbg.y
--- programs/winedbg/dbg.y	1 Apr 2003 00:12:50 -0000	1.9
+++ programs/winedbg/dbg.y	18 Apr 2003 01:17:55 -0000
@@ -52,8 +52,6 @@
 
 %token tCONT tPASS tSTEP tLIST tNEXT tQUIT tHELP tBACKTRACE tINFO tWALK tUP tDOWN
 %token tENABLE tDISABLE tBREAK tWATCH tDELETE tSET tMODE tPRINT tEXAM tABORT tVM86
-%token tCLASS tMAPS tMODULE tSTACK tSEGMENTS tSYMBOL tREGS tWND tLOCAL tEXCEPTION
-%token tPROCESS tTHREAD tEOL tEOF
 %token tCLASS tMAPS tMODULE tSTACK tSEGMENTS tSYMBOL tREGS tWND tQUEUE tLOCAL tEXCEPTION
 %token tPROCESS tTHREAD tMODREF tEOL tEOF
 %token tFRAME tSHARE tCOND tDISPLAY tUNDISPLAY tDISASSEMBLE
@@ -124,9 +122,6 @@
     | tABORT tEOL              	{ abort(); }
     | tMODE tNUM tEOL          	{ mode_command($2); }
     | tMODE tVM86 tEOL         	{ DEBUG_CurrThread->dbg_mode = MODE_VM86; }
-    | tENABLE tNUM tEOL         { DEBUG_EnableBreakpoint( $2, TRUE ); }
-    | tDISABLE tNUM tEOL        { DEBUG_EnableBreakpoint( $2, FALSE ); }
-    | tDELETE tBREAK tNUM tEOL 	{ DEBUG_DelBreakpoint( $3 ); }
     | tBACKTRACE tEOL	       	{ DEBUG_BackTrace(DEBUG_CurrTid, TRUE); }
     | tBACKTRACE tNUM tEOL     	{ DEBUG_BackTrace($2, TRUE); }
     | tUP tEOL		       	{ DEBUG_SetFrame( curr_frame + 1 );  }
@@ -137,13 +132,6 @@
     | tSHOW tDIR tEOL	       	{ DEBUG_ShowDir(); }
     | tDIR pathname tEOL       	{ DEBUG_AddPath( $2 ); }
     | tDIR tEOL		       	{ DEBUG_NukePath(); }
-    | tDISPLAY tEOL	       	{ DEBUG_InfoDisplay(); }
-    | tDISPLAY expr tEOL       	{ DEBUG_AddDisplay($2, 1, 0); }
-    | tDISPLAY tFORMAT expr tEOL{ DEBUG_AddDisplay($3, $2 >> 8, $2 & 0xff); }
-    | tDELETE tDISPLAY tNUM tEOL{ DEBUG_DelDisplay( $3 ); }
-    | tDELETE tDISPLAY tEOL    	{ DEBUG_DelDisplay( -1 ); }
-    | tUNDISPLAY tNUM tEOL     	{ DEBUG_DelDisplay( $2 ); }
-    | tUNDISPLAY tEOL          	{ DEBUG_DelDisplay( -1 ); }
     | tCOND tNUM tEOL          	{ DEBUG_AddBPCondition($2, NULL); }
     | tCOND tNUM expr tEOL	{ DEBUG_AddBPCondition($2, $3); }
     | tSOURCE pathname tEOL     { DEBUG_Parser($2); }
@@ -157,7 +145,8 @@
     | set_command
     | x_command
     | print_command
-    | break_command
+    | break_commands
+    | display_commands
     | watch_command
     | info_command
     | walk_command
@@ -165,6 +154,20 @@
     | noprocess_state
     ;
 
+display_commands:
+    | tDISPLAY tEOL	       	{ DEBUG_InfoDisplay(); }
+    | tDISPLAY expr tEOL       	{ DEBUG_AddDisplay($2, 1, 0, FALSE); }
+    | tDISPLAY tFORMAT expr tEOL{ DEBUG_AddDisplay($3, $2 >> 8, $2 & 0xff, FALSE); }
+    | tLOCAL tDISPLAY expr tEOL	{ DEBUG_AddDisplay($3, 1, 0, TRUE); }
+    | tLOCAL tDISPLAY tFORMAT expr tEOL	{ DEBUG_AddDisplay($4, $3 >> 8, $3 & 0xff, TRUE); }
+    | tENABLE tDISPLAY tNUM tEOL{ DEBUG_EnableDisplay( $3, TRUE ); }
+    | tDISABLE tDISPLAY tNUM tEOL	{ DEBUG_EnableDisplay( $3, FALSE ); }
+    | tDELETE tDISPLAY tNUM tEOL{ DEBUG_DelDisplay( $3 ); }
+    | tDELETE tDISPLAY tEOL    	{ DEBUG_DelDisplay( -1 ); }
+    | tUNDISPLAY tNUM tEOL     	{ DEBUG_DelDisplay( $2 ); }
+    | tUNDISPLAY tEOL          	{ DEBUG_DelDisplay( -1 ); }
+    ;
+
 set_command:
       tSET lval_addr '=' expr_value tEOL { DEBUG_WriteMemory(&$2, $4); }
     | tSET '+' tIDENTIFIER tEOL { DEBUG_DbgChannel(TRUE, NULL, $3); }
@@ -210,12 +213,18 @@
     | tPRINT tFORMAT expr_addr tEOL { DEBUG_Print( &$3, $2 >> 8, $2 & 0xff, 0 ); }
     ;
 
-break_command:
+break_commands:
       tBREAK '*' expr_addr tEOL{ DEBUG_AddBreakpointFromValue( &$3 ); }
     | tBREAK identifier tEOL   { DEBUG_AddBreakpointFromId($2, -1); }
     | tBREAK identifier ':' tNUM tEOL  { DEBUG_AddBreakpointFromId($2, $4); }
     | tBREAK tNUM tEOL	       { DEBUG_AddBreakpointFromLineno($2); }
     | tBREAK tEOL              { DEBUG_AddBreakpointFromLineno(-1); }
+    | tENABLE tNUM tEOL         { DEBUG_EnableBreakpoint( $2, TRUE ); }
+    | tENABLE tBREAK tNUM tEOL	{ DEBUG_EnableBreakpoint( $3, TRUE ); }
+    | tDISABLE tNUM tEOL        { DEBUG_EnableBreakpoint( $2, FALSE ); }
+    | tDISABLE tBREAK tNUM tEOL	{ DEBUG_EnableBreakpoint( $3, FALSE ); }
+    | tDELETE tNUM tEOL 	{ DEBUG_DelBreakpoint( $2 ); }
+    | tDELETE tBREAK tNUM tEOL 	{ DEBUG_DelBreakpoint( $3 ); }
     ;
 
 watch_command:
Index: programs/winedbg/display.c
===================================================================
RCS file: /home/wine/wine/programs/winedbg/display.c,v
retrieving revision 1.1
diff -u -r1.1 display.c
--- programs/winedbg/display.c	13 Sep 2002 17:54:28 -0000	1.1
+++ programs/winedbg/display.c	18 Apr 2003 01:18:00 -0000
@@ -2,6 +2,7 @@
  * File display.c - display handling for Wine internal debugger.
  *
  * Copyright (C) 1997, Eric Youngdale.
+ * Improved a bit by Michał Mirosław.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -27,130 +28,172 @@
 
 #include <stdarg.h>
 
-#define MAX_DISPLAY 25
+#define DISPTAB_DELTA 8		/* needs to be power of 2, search for MARK to see why :) */
 
-struct display
-{
-  struct expr *	exp;
-  int		count;
-  char		format;
+struct display {
+	struct expr *exp;
+	int count;
+	char format;
+	char enabled;
+	struct name_hash *function_name;
 };
 
-static struct display displaypoints[MAX_DISPLAY];
+static struct display *displaypoints = NULL;
+static unsigned int maxdisplays = 0, ndisplays = 0;
 
-int
-DEBUG_AddDisplay(struct expr * exp, int count, char format)
+static struct name_hash *DEBUG_GetCurrentFrameFunctionName(void)
 {
-  int i;
+	struct name_hash *name;
+	unsigned int eip, ebp;
 
-  /*
-   * First find a slot where we can store this display.
-   */
-  for(i=0; i < MAX_DISPLAY; i++ )
-    {
-      if( displaypoints[i].exp == NULL )
-	{
-	  displaypoints[i].exp = DEBUG_CloneExpr(exp);
-	  displaypoints[i].count  = count;
-	  displaypoints[i].format = format;
-	  break;
-	}
-    }
-
-  return TRUE;
-}
-
-int
-DEBUG_InfoDisplay(void)
-{
-  int i;
-
-  /*
-   * First find a slot where we can store this display.
-   */
-  for(i=0; i < MAX_DISPLAY; i++ )
-    {
-      if( displaypoints[i].exp != NULL )
-	{
-	  DEBUG_Printf(DBG_CHN_MESG, "%d : ", i+1);
-	  DEBUG_DisplayExpr(displaypoints[i].exp);
-	  DEBUG_Printf(DBG_CHN_MESG, "\n");
-	}
-    }
-
-  return TRUE;
-}
-
-int
-DEBUG_DoDisplay(void)
-{
-  DBG_VALUE	value;
-  int		i;
-
-  /*
-   * First find a slot where we can store this display.
-   */
-  for(i=0; i < MAX_DISPLAY; i++ )
-    {
-      if( displaypoints[i].exp != NULL )
-	{
-	  value = DEBUG_EvalExpr(displaypoints[i].exp);
-	  if( value.type == NULL )
-	    {
-	      DEBUG_Printf(DBG_CHN_MESG, "Unable to evaluate expression ");
-	      DEBUG_DisplayExpr(displaypoints[i].exp);
-	      DEBUG_Printf(DBG_CHN_MESG, "\nDisabling...\n");
-	      DEBUG_DelDisplay(i);
-	    }
-	  else
-	    {
-	      DEBUG_Printf(DBG_CHN_MESG, "%d  : ", i + 1);
-	      DEBUG_DisplayExpr(displaypoints[i].exp);
-	      DEBUG_Printf(DBG_CHN_MESG, " = ");
-	      if( displaypoints[i].format == 'i' )
-		{
-		  DEBUG_ExamineMemory( &value,
-				       displaypoints[i].count,
-				       displaypoints[i].format);
-		}
-	      else
-		{
-		  DEBUG_Print( &value,
-			       displaypoints[i].count,
-			       displaypoints[i].format, 0);
+	if (DEBUG_GetCurrentFrame(&name, &eip, &ebp))
+		return name;
+	return NULL;
+}
+
+int DEBUG_AddDisplay(struct expr *exp, int count, char format, int in_frame)
+{
+	int i;
+
+	for (i = 0; i < ndisplays; i++)
+		if (displaypoints[i].exp == NULL)
+			break;
+
+	if (i == maxdisplays)	/* no space left - expand */
+		displaypoints = DBG_realloc(displaypoints,
+			(maxdisplays += DISPTAB_DELTA) * sizeof(*displaypoints));
+
+	if (i == ndisplays)
+		++ndisplays;
+
+	displaypoints[i].exp = DEBUG_CloneExpr(exp);
+	displaypoints[i].count = count;
+	displaypoints[i].format = format;
+	displaypoints[i].enabled = TRUE;
+	displaypoints[i].function_name =
+		(in_frame ? DEBUG_GetCurrentFrameFunctionName() : NULL);
+
+	return TRUE;
+}
+
+int DEBUG_InfoDisplay(void)
+{
+	int i;
+
+	for (i = 0; i < ndisplays; i++) {
+		if (displaypoints[i].exp == NULL)
+			continue;
+
+		if (displaypoints[i].function_name)
+			DEBUG_Printf(DBG_CHN_MESG, "%d in %s%s : ", i + 1,
+				DEBUG_GetSymbolName(displaypoints[i].function_name),
+				(displaypoints[i].enabled ?
+					(displaypoints[i].function_name != DEBUG_GetCurrentFrameFunctionName() ?
+						" (out of scope)" : "")
+					: " (disabled)")
+				);
+		else
+			DEBUG_Printf(DBG_CHN_MESG, "%d%s : ", i + 1,
+				(displaypoints[i].enabled ? "" : " (disabled)"));
+		DEBUG_DisplayExpr(displaypoints[i].exp);
+		DEBUG_Printf(DBG_CHN_MESG, "\n");
+	}
+
+	return TRUE;
+}
+
+void DEBUG_PrintOneDisplay(int i)
+{
+	DBG_VALUE value;
+
+	if (displaypoints[i].enabled) {
+		value = DEBUG_EvalExpr(displaypoints[i].exp);
+		if (value.type == NULL) {
+			DEBUG_Printf(DBG_CHN_MESG, "Unable to evaluate expression ");
+			DEBUG_DisplayExpr(displaypoints[i].exp);
+			DEBUG_Printf(DBG_CHN_MESG, "\nDisabling display %d ...\n", i + 1);
+			displaypoints[i].enabled = FALSE;
+			return;
 		}
-	    }
 	}
-    }
 
-  return TRUE;
+	DEBUG_Printf(DBG_CHN_MESG, "%d  : ", i + 1);
+	DEBUG_DisplayExpr(displaypoints[i].exp);
+	DEBUG_Printf(DBG_CHN_MESG, " = ");
+	if (!displaypoints[i].enabled)
+		DEBUG_Printf(DBG_CHN_MESG, "(disabled)\n");
+	else
+	if (displaypoints[i].format == 'i')
+		DEBUG_ExamineMemory(&value, displaypoints[i].count, displaypoints[i].format);
+	else
+		DEBUG_Print(&value, displaypoints[i].count, displaypoints[i].format, 0);
 }
 
-int
-DEBUG_DelDisplay(int displaynum)
-{
-  int i;
-
-  if( displaynum >= MAX_DISPLAY || displaynum == 0 || displaynum < -1 )
-    {
-      DEBUG_Printf(DBG_CHN_MESG, "Invalid display number\n");
-      return TRUE;
-    }
-  if( displaynum == -1 )
-    {
-      for(i=0; i < MAX_DISPLAY; i++ )
-	{
-	  if( displaypoints[i].exp != NULL )
-	    {
-	      DEBUG_FreeExpr(displaypoints[i].exp);
-	      displaypoints[i].exp = NULL;
-	    }
-	}
-    }
-  else if( displaypoints[displaynum - 1].exp != NULL )
-    {
-      DEBUG_FreeExpr(displaypoints[displaynum - 1].exp);
-      displaypoints[displaynum - 1].exp = NULL;
-    }
-  return TRUE;
+int DEBUG_DoDisplay(void)
+{
+	int i;
+	struct name_hash *cur_function_name = DEBUG_GetCurrentFrameFunctionName();
+
+	for (i = 0; i < ndisplays; i++) {
+		if (displaypoints[i].exp == NULL || !displaypoints[i].enabled)
+			continue;
+		if (displaypoints[i].function_name
+		    && displaypoints[i].function_name != cur_function_name)
+			continue;
+		DEBUG_PrintOneDisplay(i);
+	}
+
+	return TRUE;
+}
+
+int DEBUG_DelDisplay(int displaynum)
+{
+	int i;
+
+	if (displaynum > ndisplays || displaynum == 0 || displaynum < -1
+	    || displaypoints[displaynum - 1].exp == NULL) {
+		DEBUG_Printf(DBG_CHN_MESG, "Invalid display number\n");
+		return TRUE;
+	}
+
+	if (displaynum == -1) {
+		for (i = 0; i < ndisplays; i++) {
+			if (displaypoints[i].exp != NULL) {
+				DEBUG_FreeExpr(displaypoints[i].exp);
+				displaypoints[i].exp = NULL;
+			}
+		}
+		displaypoints = DBG_realloc(displaypoints,
+			(maxdisplays = DISPTAB_DELTA) * sizeof(*displaypoints));
+		ndisplays = 0;
+	} else if (displaypoints[--displaynum].exp != NULL) {
+		DEBUG_FreeExpr(displaypoints[displaynum].exp);
+		displaypoints[displaynum].exp = NULL;
+		while (displaynum == ndisplays - 1
+		    && displaypoints[displaynum].exp == NULL)
+			--ndisplays, --displaynum;
+		if (maxdisplays - ndisplays >= 2 * DISPTAB_DELTA) {
+			maxdisplays = (ndisplays + DISPTAB_DELTA - 1) & ~(DISPTAB_DELTA - 1); /* MARK */
+			displaypoints = DBG_realloc(displaypoints,
+				maxdisplays * sizeof(*displaypoints));
+		}
+	}
+	return TRUE;
 }
+
+int DEBUG_EnableDisplay(int displaynum, int enable)
+{
+	--displaynum;
+	if (displaynum >= ndisplays || displaynum < 0 || displaypoints[displaynum].exp == NULL) {
+		DEBUG_Printf(DBG_CHN_MESG, "Invalid display number\n");
+		return TRUE;
+	}
+
+	displaypoints[displaynum].enabled = enable;
+	if (!displaypoints[displaynum].function_name
+	    || displaypoints[displaynum].function_name == DEBUG_GetCurrentFrameFunctionName())
+		DEBUG_PrintOneDisplay(displaynum);
+
+	return TRUE;
+}
+


[Index of Archives]     [Gimp for Windows]     [Red Hat]     [Samba]     [Yosemite Camping]     [Graphics Cards]     [Wine Home]

  Powered by Linux