Browse Source

Merge pull request #1252 from Kelimion/bug-report

Add new `odin report` command.
gingerBill 3 years ago
parent
commit
efe05b3e13
5 changed files with 678 additions and 21 deletions
  1. 9 0
      .github/workflows/ci.yml
  2. 3 0
      Makefile
  3. 641 0
      src/bug_report.cpp
  4. 8 7
      src/build_settings.cpp
  5. 17 14
      src/main.cpp

+ 9 - 0
.github/workflows/ci.yml

@@ -13,6 +13,9 @@ jobs:
       - name: Odin version
         run: ./odin version
         timeout-minutes: 1
+      - name: Odin report
+        run: ./odin report
+        timeout-minutes: 1
       - name: Odin check
         run: ./odin check examples/demo/demo.odin -vet
         timeout-minutes: 10
@@ -39,6 +42,9 @@ jobs:
       - name: Odin version
         run: ./odin version
         timeout-minutes: 1
+      - name: Odin report
+        run: ./odin report
+        timeout-minutes: 1
       - name: Odin check
         run: ./odin check examples/demo/demo.odin -vet
         timeout-minutes: 10
@@ -57,6 +63,9 @@ jobs:
       - name: Odin version
         run: ./odin version
         timeout-minutes: 1
+      - name: Odin report
+        run: ./odin report
+        timeout-minutes: 1
       - name: Odin check
         shell: cmd
         run: |

+ 3 - 0
Makefile

@@ -40,6 +40,9 @@ all: debug demo
 demo:
 	./odin run examples/demo/demo.odin
 
+report:
+	./odin report
+
 debug:
 	$(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -g $(LDFLAGS) -o odin
 

+ 641 - 0
src/bug_report.cpp

@@ -0,0 +1,641 @@
+/*
+	Gather and print platform and version info to help with reporting Odin bugs.
+*/
+
+#if !defined(GB_COMPILER_MSVC)
+	#if defined(GB_CPU_X86)
+		#include <cpuid.h>
+	#endif
+#endif
+
+#if defined(GB_SYSTEM_LINUX)
+	#include <sys/utsname.h>
+	#include <sys/sysinfo.h>
+#endif
+
+#if defined(GB_SYSTEM_OSX)
+	#include <sys/sysctl.h>
+#endif
+
+/*
+	NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`.
+*/
+#if defined(GB_SYSTEM_WINDOWS)
+void report_windows_product_type(DWORD ProductType) {
+	switch (ProductType) {
+	case PRODUCT_ULTIMATE:
+		gb_printf("Ultimate");
+		break;
+
+	case PRODUCT_HOME_BASIC:
+		gb_printf("Home Basic");
+		break;
+
+	case PRODUCT_HOME_PREMIUM:
+		gb_printf("Home Premium");
+		break;
+
+	case PRODUCT_ENTERPRISE:
+		gb_printf("Enterprise");
+		break;
+
+	case PRODUCT_HOME_BASIC_N:
+		gb_printf("Home Basic N");
+		break;
+
+	case PRODUCT_BUSINESS:
+		gb_printf("Business");
+		break;
+
+	case PRODUCT_STANDARD_SERVER:
+		gb_printf("Standard Server");
+		break;
+
+	case PRODUCT_DATACENTER_SERVER:
+		gb_printf("Datacenter");
+		break;
+
+	case PRODUCT_SMALLBUSINESS_SERVER:
+		gb_printf("Windows Small Business Server");
+		break;
+
+	case PRODUCT_ENTERPRISE_SERVER:
+		gb_printf("Enterprise Server");
+		break;
+
+	case PRODUCT_STARTER:
+		gb_printf("Starter");
+		break;
+
+	case PRODUCT_DATACENTER_SERVER_CORE:
+		gb_printf("Datacenter Server Core");
+		break;
+
+	case PRODUCT_STANDARD_SERVER_CORE:
+		gb_printf("Server Standard Core");
+		break;
+
+	case PRODUCT_ENTERPRISE_SERVER_CORE:
+		gb_printf("Enterprise Server Core");
+		break;
+
+	case PRODUCT_BUSINESS_N:
+		gb_printf("Business N");
+		break;
+
+	case PRODUCT_HOME_SERVER:
+		gb_printf("Home Server");
+		break;
+
+	case PRODUCT_SERVER_FOR_SMALLBUSINESS:
+		gb_printf("Windows Server 2008 for Windows Essential Server Solutions");
+		break;
+
+	case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
+		gb_printf("Small Business Server Premium");
+		break;
+
+	case PRODUCT_HOME_PREMIUM_N:
+		gb_printf("Home Premium N");
+		break;
+
+	case PRODUCT_ENTERPRISE_N:
+		gb_printf("Enterprise N");
+		break;
+
+	case PRODUCT_ULTIMATE_N:
+		gb_printf("Ultimate N");
+		break;
+
+	case PRODUCT_HYPERV:
+		gb_printf("HyperV");
+		break;
+
+	case PRODUCT_STARTER_N:
+		gb_printf("Starter N");
+		break;
+
+	case PRODUCT_PROFESSIONAL:
+		gb_printf("Professional");
+		break;
+
+	case PRODUCT_PROFESSIONAL_N:
+		gb_printf("Professional N");
+		break;
+
+	case PRODUCT_UNLICENSED:
+		gb_printf("Unlicensed");
+		break;
+
+	default:
+		gb_printf("Unknown Edition (%08x)", ProductType);
+	}
+}
+#endif
+
+void odin_cpuid(int leaf, int result[]) {
+	#if defined(GB_COMPILER_MSVC)
+		__cpuid(result, leaf);
+	#else
+		__get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]);
+	#endif
+}
+
+void report_cpu_info() {
+	gb_printf("\tCPU:  ");
+
+	#if defined(GB_CPU_X86)
+
+	/*
+		Get extended leaf info
+	*/
+	int cpu[4];
+
+	odin_cpuid(0x80000000, &cpu[0]);
+	int number_of_extended_ids = cpu[0];
+
+	int brand[0x12] = {};
+
+	/*
+		Read CPU brand if supported.
+	*/
+	if (number_of_extended_ids >= 0x80000004) {
+		odin_cpuid(0x80000002, &brand[0]);
+		odin_cpuid(0x80000003, &brand[4]);
+		odin_cpuid(0x80000004, &brand[8]);
+
+		/*
+			Some CPUs like `      Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz` may include leading spaces. Trim them.
+		*/
+		char * brand_name = (char *)&brand[0];
+		for (; brand_name[0] == ' '; brand_name++) {}
+
+		gb_printf("%s\n", brand_name);
+	} else {
+		gb_printf("Unable to retrieve.\n");
+	}
+
+	#elif defined(GB_CPU_ARM)
+		/*
+			TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`.
+		*/
+		#if defined(GB_ARCH_64_BIT)
+			gb_printf("ARM64\n");
+		#else
+			gb_printf("ARM\n");
+		#endif
+	#else
+		gb_printf("Unknown\n");
+	#endif
+}
+
+/*
+	Report the amount of installed RAM.
+*/
+void report_ram_info() {
+
+	gb_printf("\tRAM:  ");
+
+	#if defined(GB_SYSTEM_WINDOWS)
+		MEMORYSTATUSEX statex;
+		statex.dwLength = sizeof(statex);
+		GlobalMemoryStatusEx (&statex);
+
+		gb_printf("%lld MiB\n", statex.ullTotalPhys / gb_megabytes(1));
+
+	#elif defined(GB_SYSTEM_LINUX)
+		/*
+			Retrieve RAM info using `sysinfo()`, 
+		*/
+		struct sysinfo info;
+		int result = sysinfo(&info);
+
+		if (result == 0x0) {
+			gb_printf("%lu MiB\n", info.totalram * info.mem_unit / gb_megabytes(1));
+		} else {
+			gb_printf("Unknown.\n");
+		}
+	#elif defined(GB_SYSTEM_OSX)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int sysctls[] = { CTL_HW, HW_MEMSIZE };
+		if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
+		}
+	#else
+		gb_printf("Unknown.\n");
+	#endif
+}
+
+/*
+	NOTE(Jeroen): `odin report` prints some system information for easier bug reporting.
+*/
+void print_bug_report_help() {
+	gb_printf("Where to find more information and get into contact when you encounter a bug:\n\n");
+	gb_printf("\tWebsite: https://odin-lang.org\n");
+	gb_printf("\tGitHub:  https://github.com/odin-lang/Odin/issues\n");
+	/*
+		Uncomment and update URL once we have a Discord vanity URL. For now people can get here from the site.
+		gb_printf("\tDiscord: https://discord.com/invite/sVBPHEv\n");
+	*/
+	gb_printf("\n\n");
+
+	gb_printf("Useful information to add to a bug report:\n\n");
+
+	gb_printf("\tOdin: %.*s", LIT(ODIN_VERSION));
+
+	#ifdef NIGHTLY
+	gb_printf("-nightly");
+	#endif
+
+	#ifdef GIT_SHA
+	gb_printf(":%s", GIT_SHA);
+	#endif
+
+	gb_printf("\n");
+
+	/*
+		Print OS information.
+	*/
+	gb_printf("\tOS:   ");
+
+	#if defined(GB_SYSTEM_WINDOWS)
+		/*
+			NOTE(Jeroen): 
+				`GetVersionEx`  will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
+				`RtlGetVersion` will return the true version.
+
+				Rather than include the WinDDK, we ask the kernel directly.
+
+				`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)
+
+		*/
+		OSVERSIONINFOEXW osvi;
+		ZeroMemory(&osvi, sizeof(OSVERSIONINFOEXW));
+		osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
+
+		typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(OSVERSIONINFOW*);
+		typedef BOOL (WINAPI* GetProductInfoPtr)(DWORD dwOSMajorVersion, DWORD dwOSMinorVersion, DWORD dwSpMajorVersion, DWORD dwSpMinorVersion, PDWORD pdwReturnedProductType);
+
+		// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion
+		RtlGetVersionPtr  RtlGetVersion  =  (RtlGetVersionPtr)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion");
+		// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getproductinfo
+		GetProductInfoPtr GetProductInfo = (GetProductInfoPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo");
+
+		NTSTATUS status  = {};
+		DWORD ProductType = {};
+		if (RtlGetVersion != nullptr) {
+			status = RtlGetVersion((OSVERSIONINFOW*)&osvi);
+		}
+
+		if (RtlGetVersion == nullptr || status != 0x0) {
+			gb_printf("Windows (Unknown Version)");
+		} else {
+			if (GetProductInfo != nullptr) {
+				GetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &ProductType);
+			}
+
+			if (false) {
+				gb_printf("dwMajorVersion:    %d\n", osvi.dwMajorVersion);
+				gb_printf("dwMinorVersion:    %d\n", osvi.dwMinorVersion);
+				gb_printf("dwBuildNumber:     %d\n", osvi.dwBuildNumber);
+				gb_printf("dwPlatformId:      %d\n", osvi.dwPlatformId);
+				gb_printf("wServicePackMajor: %d\n", osvi.wServicePackMajor);
+				gb_printf("wServicePackMinor: %d\n", osvi.wServicePackMinor);
+				gb_printf("wSuiteMask:        %d\n", osvi.wSuiteMask);
+				gb_printf("wProductType:      %d\n", osvi.wProductType);
+			}
+
+			gb_printf("Windows ");
+
+			switch (osvi.dwMajorVersion) {
+			case 10:
+				/*
+					Windows 10 (Pro), Windows 2016 Server, Windows 2019 Server, Windows 2022 Server
+				*/
+				switch (osvi.wProductType) {
+				case VER_NT_WORKSTATION: // Workstation
+					if (osvi.dwBuildNumber < 22000) {
+						gb_printf("10 ");
+					} else {
+						gb_printf("11 ");
+					}
+					
+					report_windows_product_type(ProductType);
+
+					break;
+				default: // Server or Domain Controller
+					switch(osvi.dwBuildNumber) {
+					case 14393:
+						gb_printf("2016 Server");
+						break;
+					case 17763:
+						gb_printf("2019 Server");
+						break;
+					case 20348:
+						gb_printf("2022 Server");
+						break;
+					default:
+						gb_printf("Unknown Server");
+						break;
+					}
+				}
+				break;
+			case 6:
+				switch (osvi.dwMinorVersion) {
+					case 0:
+						switch (osvi.wProductType) {
+							case VER_NT_WORKSTATION:
+								gb_printf("Windows Vista ");
+								report_windows_product_type(ProductType);
+								break;
+							case 3:
+								gb_printf("Windows Server 2008");
+								break;
+						}
+						break;
+
+					case 1:
+						switch (osvi.wProductType) {
+							case VER_NT_WORKSTATION:
+								gb_printf("Windows 7 ");
+								report_windows_product_type(ProductType);
+								break;
+							case 3:
+								gb_printf("Windows Server 2008 R2");
+								break;
+						}
+						break;
+					case 2:
+						switch (osvi.wProductType) {
+							case VER_NT_WORKSTATION:
+								gb_printf("Windows 8 ");
+								report_windows_product_type(ProductType);
+								break;
+							case 3:
+								gb_printf("Windows Server 2012");
+								break;
+						}
+						break;
+					case 3:
+						switch (osvi.wProductType) {
+							case VER_NT_WORKSTATION:
+								gb_printf("Windows 8.1 ");
+								report_windows_product_type(ProductType);
+								break;
+							case 3:
+								gb_printf("Windows Server 2012 R2");
+								break;
+						}
+						break;
+				}
+				break;
+			case 5:
+				switch (osvi.dwMinorVersion) {
+					case 0:
+						gb_printf("Windows 2000");
+						break;
+					case 1:
+						gb_printf("Windows XP");
+						break;
+					case 2:
+						gb_printf("Windows Server 2003");
+						break;
+				}
+				break;
+			default:
+				break;
+			}
+
+			/*
+				Grab Windows DisplayVersion (like 20H02)
+			*/
+			LPDWORD ValueType = {};
+			DWORD   UBR;
+			char    DisplayVersion[256];
+			DWORD   ValueSize = 256;
+
+			status = RegGetValue(
+				HKEY_LOCAL_MACHINE,
+				TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
+				TEXT("DisplayVersion"),
+				RRF_RT_REG_SZ,
+				ValueType,
+				&DisplayVersion,
+				&ValueSize
+			);
+
+			if (status == 0x0) {
+				gb_printf(" (version: %s)", &DisplayVersion);
+			}
+
+			/*
+				Now print build number.
+			*/
+			gb_printf(", build %d", osvi.dwBuildNumber);
+
+			ValueSize = sizeof(UBR);
+			status = RegGetValue(
+				HKEY_LOCAL_MACHINE,
+				TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
+				TEXT("UBR"),
+				RRF_RT_REG_DWORD,
+				ValueType,
+				&UBR,
+				&ValueSize
+			);
+
+			if (status == 0x0) {
+				gb_printf(".%d", UBR);
+			}
+			gb_printf("\n");
+		}
+
+	#elif defined(GB_SYSTEM_LINUX)
+		/*
+			Try to parse `/usr/lib/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
+		*/
+		gbAllocator a = heap_allocator();
+
+		gbFileContents release = gb_file_read_contents(a, 1, "/usr/lib/os-release");
+		defer (gb_file_free_contents(&release));
+
+		b32 found = 0;
+		if (release.size) {
+			char *start        = (char *)release.data;
+			char *end          = (char *)release.data + release.size;
+			const char *needle = "PRETTY_NAME=\"";
+			isize needle_len   = gb_strlen((needle));
+		
+			char *c = start;
+			for (; c < end; c++) {
+				if (gb_strncmp(c, needle, needle_len) == 0) {
+					found = 1;
+					start = c + needle_len;
+					break;
+				}
+			}
+
+			if (found) {
+				for (c = start; c < end; c++) {
+					if (*c == '"') {
+						// Found the closing quote. Replace it with \0
+						*c = 0;
+						gb_printf("%s", (char *)start);
+						break;
+					} else if (*c == '\n') {
+						found = 0;
+					}
+				}
+			}
+		}
+
+		if (!found) {
+			gb_printf("Unknown Linux Distro");
+		}
+
+		/*
+			Print kernel info using `uname()` syscall, https://linux.die.net/man/2/uname
+		*/
+		char buffer[1024];
+		uname((struct utsname *)&buffer[0]);
+
+		struct utsname *info;
+		info = (struct utsname *)&buffer[0];
+
+		gb_printf(", %s %s\n", info->sysname, info->release);
+
+	#elif defined(GB_SYSTEM_OSX)
+		b32 found = 1;
+		uint32_t major, minor;
+
+		#define osx_version_buffer 100
+		char buffer[osx_version_buffer];
+		size_t buffer_size = osx_version_buffer - 1;
+		#undef osx_version_buffer
+
+		int sysctls[] = { CTL_KERN, KERN_OSRELEASE };
+		if (sysctl(sysctls, 2, buffer, &buffer_size, NULL, 0) == -1) {
+			found = 0;
+		} else {
+			if (sscanf(buffer, "%u.%u", &major, &minor) != 2) {
+				found = 0;
+			}
+		}
+
+		if (found) {
+			switch (major) {
+			case 21:
+				/*
+					macOS Big Sur and newer
+				*/
+				major -= 9;
+
+				gb_printf("macOS 12.0.%d Monterey\n", minor);
+
+				break;
+			case 20:
+				/*
+					macOS Big Sur and newer
+				*/
+				major -= 9;
+
+				gb_printf("macOS 11.%d Big Sur\n", minor);
+
+				break;
+			case 14:
+				/*
+					macOS 10.1.1 and newer
+				*/
+				major -= 4;
+
+				switch (minor) {
+				case 1:
+					gb_printf("macOS Puma 10.1\n");
+					break;
+
+				case 2:
+					gb_printf("macOS Jaguar 10.2\n");
+					break;
+
+				case 3:
+					gb_printf("macOS Panther 10.3\n");
+					break;
+
+				case 4:
+					gb_printf("macOS Tiger 10.4\n");
+					break;
+
+				case 5:
+					gb_printf("macOS Leopard 10.5\n");
+					break;
+
+				case 6:
+					gb_printf("macOS Snow Leopard 10.6\n");
+					break;
+
+				case 7:
+					gb_printf("macOS Lion 10.7\n");
+					break;
+
+				case 8:
+					gb_printf("macOS Mountain Lion 10.8\n");
+					break;
+
+				case 9:
+					gb_printf("macOS Mavericks 10.9\n");
+					break;
+
+				case 10:
+					gb_printf("macOS Yosemite 10.10\n");
+					break;
+
+				case 11:
+					gb_printf("macOS El Capitan 10.11\n");
+					break;
+
+				case 12:
+					gb_printf("macOS Sierra 10.12\n");
+					break;
+
+				case 13:
+					gb_printf("macOS High Sierra 10.13\n");
+					break;
+
+				case 14:
+					gb_printf("macOS Mojave 10.14\n");
+					break;
+
+				case 15:
+					gb_printf("macOS Catalina 10.15\n");
+					break;
+
+				default:
+					gb_printf("macOS %d.%d\n", major, minor);
+					break;
+				}
+
+				break;
+			default:
+				gb_printf("macOS Unknown (kernel version %d.%d)\n", major, minor);
+				break;
+			}
+		} else {
+			gb_printf("macOS: Unknown\n");
+		}			
+	#else
+		gb_printf("Unknown\n");
+
+	#endif
+
+	/*
+		Now print CPU info.
+	*/
+	report_cpu_info();
+
+	/*
+		And RAM info.
+	*/
+	report_ram_info();
+}

+ 8 - 7
src/build_settings.cpp

@@ -120,15 +120,16 @@ enum BuildModeKind {
 };
 
 enum CommandKind : u32 {
-	Command_run     = 1<<0,
-	Command_build   = 1<<1,
-	Command_check   = 1<<3,
-	Command_query   = 1<<4,
-	Command_doc     = 1<<5,
-	Command_version = 1<<6,
-	Command_test    = 1<<7,
+	Command_run             = 1<<0,
+	Command_build           = 1<<1,
+	Command_check           = 1<<3,
+	Command_query           = 1<<4,
+	Command_doc             = 1<<5,
+	Command_version         = 1<<6,
+	Command_test            = 1<<7,
 	
 	Command_strip_semicolon = 1<<8,
+	Command_bug_report      = 1<<9,
 
 	Command__does_check = Command_run|Command_build|Command_check|Command_query|Command_doc|Command_test|Command_strip_semicolon,
 	Command__does_build = Command_run|Command_build|Command_test,

+ 17 - 14
src/main.cpp

@@ -64,6 +64,8 @@ gb_global Timings global_timings = {0};
 #include "microsoft_craziness.h"
 #endif
 
+#include "bug_report.cpp"
+
 
 // NOTE(bill): 'name' is used in debugging and profiling modes
 i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
@@ -96,8 +98,8 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
 
 	wcmd = string_to_string16(permanent_allocator(), make_string(cast(u8 *)cmd_line, cmd_len-1));
 	if (CreateProcessW(nullptr, wcmd.text,
-	                   nullptr, nullptr, true, 0, nullptr, nullptr,
-	                   &start_info, &pi)) {
+					   nullptr, nullptr, true, 0, nullptr, nullptr,
+					   &start_info, &pi)) {
 		WaitForSingleObject(pi.hProcess, INFINITE);
 		GetExitCodeProcess(pi.hProcess, cast(DWORD *)&exit_code);
 
@@ -211,7 +213,7 @@ i32 linker_stage(lbGenerator *gen) {
 				String lib = m->foreign_library_paths[i];
 				GB_ASSERT(lib.len < gb_count_of(lib_str_buf)-1);
 				gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
-				            " \"%.*s\"", LIT(lib));
+							" \"%.*s\"", LIT(lib));
 				lib_str = gb_string_appendc(lib_str, lib_str_buf);
 			}
 		}
@@ -220,7 +222,7 @@ i32 linker_stage(lbGenerator *gen) {
 			String lib = gen->default_module.foreign_library_paths[i];
 			GB_ASSERT(lib.len < gb_count_of(lib_str_buf)-1);
 			gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
-			            " \"%.*s\"", LIT(lib));
+						" \"%.*s\"", LIT(lib));
 			lib_str = gb_string_appendc(lib_str, lib_str_buf);
 		}
 
@@ -319,7 +321,7 @@ i32 linker_stage(lbGenerator *gen) {
 			);
 			  
 			  if (result) {
-			  	return result;
+				return result;
 			  }
 		}
 	#else
@@ -512,10 +514,6 @@ Array<String> setup_args(int argc, char const **argv) {
 #endif
 }
 
-
-
-
-
 void print_usage_line(i32 indent, char const *fmt, ...) {
 	while (indent --> 0) {
 		gb_printf_err("\t");
@@ -539,6 +537,7 @@ void usage(String argv0) {
 	print_usage_line(1, "query     parse, type check, and output a .json file containing information about the program");
 	print_usage_line(1, "doc       generate documentation .odin file, or directory of .odin files");
 	print_usage_line(1, "version   print version");
+	print_usage_line(1, "report    print information useful to reporting a bug");
 	print_usage_line(0, "");
 	print_usage_line(0, "For more information of flags, apply the flag to see what is possible");
 	print_usage_line(1, "-help");
@@ -855,12 +854,12 @@ bool parse_build_flags(Array<String> args) {
 							break;
 						case BuildFlagParam_Boolean: {
 							if (str_eq_ignore_case(param, str_lit("t")) ||
-							    str_eq_ignore_case(param, str_lit("true")) ||
-							    param == "1") {
+								str_eq_ignore_case(param, str_lit("true")) ||
+								param == "1") {
 								value = exact_value_bool(true);
 							} else if (str_eq_ignore_case(param, str_lit("f")) ||
-							           str_eq_ignore_case(param, str_lit("false")) ||
-							           param == "0") {
+									   str_eq_ignore_case(param, str_lit("false")) ||
+									   param == "0") {
 								value = exact_value_bool(false);
 							} else {
 								gb_printf_err("Invalid flag parameter for '%.*s' : '%.*s'\n", LIT(name), LIT(param));
@@ -2372,6 +2371,10 @@ int main(int arg_count, char const **arg_ptr) {
 
 		gb_printf("\n");
 		return 0;
+	} else if (command == "report") {
+		build_context.command_kind = Command_bug_report;
+		print_bug_report_help();
+		return 0;
 	} else {
 		usage(args[0]);
 		return 1;
@@ -2395,7 +2398,7 @@ int main(int arg_count, char const **arg_ptr) {
 	// NOTE(bill): add 'shared' directory if it is not already set
 	if (!find_library_collection_path(str_lit("shared"), nullptr)) {
 		add_library_collection(str_lit("shared"),
-		                       get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
+							   get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
 	}