From 639d6d86b904a7356e4557c5cd00048f6244ae31 Mon Sep 17 00:00:00 2001 From: Frank Robijn <13610628+frobijn@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:34:57 +0100 Subject: [PATCH] `GlobalExclusiveDeviceAccess` added to Visual Studio extension (#834) --- .editorconfig | 213 ++++++++++ .../AutomaticUpdates/UpdateManager.cs | 375 +++++++++--------- .../AutomaticUpdates/UpdateManager.cs | 374 +++++++++-------- .../NanoFrameworkMoniker.imagemanifest | 2 +- spelling_exclusion.dic | 2 + .../CorDebug/CorDebugProcess.cs | 247 ++++++------ .../NanoDebuggerLaunchProvider.cs | 96 +++-- .../DeployProvider/DeployProvider.cs | 32 +- .../INanoDeviceCommService.cs | 12 +- .../NanoDeviceCommService.cs | 20 +- .../DeviceExplorerCommand.cs | 76 +++- 11 files changed, 894 insertions(+), 555 deletions(-) create mode 100644 .editorconfig create mode 100644 spelling_exclusion.dic diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a5a9d5fe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,213 @@ +# EditorConfig for Visual Studio 2022: https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022 + +# This is a top-most .editorconfig file +root = true + +#===================================================== +# +# nanoFramework specific settings +# +# +#===================================================== +[*] +# Generic EditorConfig settings +end_of_line = crlf +charset = utf-8-bom + +# Visual Studio spell checker +spelling_languages = en-us +spelling_checkable_types = strings,identifiers,comments +spelling_error_severity = information +spelling_exclusion_path = spelling_exclusion.dic + +#===================================================== +# +# Settings copied from the .NET runtime +# +# https://github.com/dotnet/runtime +# +#===================================================== +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] +generated_code = true + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_collection_expression = when_types_exactly_match +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# License header +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf \ No newline at end of file diff --git a/VisualStudio.Extension-2019/AutomaticUpdates/UpdateManager.cs b/VisualStudio.Extension-2019/AutomaticUpdates/UpdateManager.cs index 8def9bc9..92444fd1 100644 --- a/VisualStudio.Extension-2019/AutomaticUpdates/UpdateManager.cs +++ b/VisualStudio.Extension-2019/AutomaticUpdates/UpdateManager.cs @@ -1,24 +1,23 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; using GalaSoft.MvvmLight.Messaging; using Microsoft.VisualStudio.Shell; using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.NFDevice; using nanoFramework.Tools.VisualStudio.Extension.FirmwareUpdate; using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Task = System.Threading.Tasks.Task; namespace nanoFramework.Tools.VisualStudio.Extension.AutomaticUpdates { public class UpdateManager { + private const int ExclusiveAccessTimeout = 3000; private static UpdateManager s_instance; private ViewModelLocator ViewModelLocator; private readonly Package _package; @@ -32,7 +31,7 @@ private UpdateManager(Package package) } public static void Initialize( - AsyncPackage package, + AsyncPackage package, ViewModelLocator vmLocator) { s_instance = new UpdateManager(package) @@ -101,257 +100,279 @@ private void LaunchUpdate(string deviceId) } } #endif - - // check if DebugEngine is available - if (nanoDevice.DebugEngine == null) - { - nanoDevice.CreateDebugEngine(); - } - - if (nanoDevice.DebugEngine == null) - { - // can't create it, quit update now - return; - } - - // add this device to the updatING list - if (!devicesUpdatING.TryAdd(deviceId, new object())) + GlobalExclusiveDeviceAccess exclusiveAccess = null; + try { - // fail to add device to list + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(nanoDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + // Can't get access, quit update for now #if DEBUG - Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} update already in progress."); + Console.WriteLine($"[Automatic Updates] Cannot access {nanoDevice.Description}, another application is using the device"); #endif + return; + } - // quit, never mind, this is not critical whatsoever - return; - } - - // better wrap this on a try-finally because a lot of things can go wrong in the process - try - { - await Task.Yield(); - var fwPackage = await GetFirmwarePackageAsync( - nanoDevice.TargetName, - nanoDevice.Platform); + // check if DebugEngine is available + if (nanoDevice.DebugEngine == null) + { + nanoDevice.CreateDebugEngine(); + } - await Task.Yield(); + if (nanoDevice.DebugEngine == null) + { + // can't create it, quit update now + return; + } - ////////////////////////////// - // STM32 targets - if (fwPackage is Stm32Firmware) + // add this device to the updatING list + if (!devicesUpdatING.TryAdd(deviceId, new object())) { - // sanity check - if (nanoDevice.DebugEngine == null) - { + // fail to add device to list #if DEBUG - Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} debug engine is not ready."); + Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} update already in progress."); #endif - // quit - return; - } - if (nanoDevice.DebugEngine.Connect( - 1000, - true, - true)) - { - Version currentClrVersion = null; + // quit, never mind, this is not critical whatsoever + return; + } + + // better wrap this on a try-finally because a lot of things can go wrong in the process + try + { + await Task.Yield(); - // try to store CLR version - if(nanoDevice.DebugEngine.IsConnectedTonanoCLR) + var fwPackage = await GetFirmwarePackageAsync( + nanoDevice.TargetName, + nanoDevice.Platform); + + await Task.Yield(); + + ////////////////////////////// + // STM32 targets + if (fwPackage is Stm32Firmware) + { + // sanity check + if (nanoDevice.DebugEngine == null) { - if (nanoDevice.DeviceInfo.Valid) - { - currentClrVersion = nanoDevice.DeviceInfo.SolutionBuildVersion; - } +#if DEBUG + Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} debug engine is not ready."); +#endif + // quit + return; } - // update conditions: - // 1. Running CLR _and_ the new version is higher - // 2. Running nanoBooter and there is no version information on the CLR (presumably because there is no CLR installed) - if (fwPackage.Version > nanoDevice.CLRVersion) + if (nanoDevice.DebugEngine.Connect( + 1000, + true, + true)) { - bool attemptToLaunchBooter = false; + Version currentClrVersion = null; + // try to store CLR version if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) { - // any update has to be handled by nanoBooter, so let's have it running - try + if (nanoDevice.DeviceInfo.Valid) { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Launching nanoBooter..."); + currentClrVersion = nanoDevice.DeviceInfo.SolutionBuildVersion; + } + } - attemptToLaunchBooter = nanoDevice.ConnectToNanoBooter(); + // update conditions: + // 1. Running CLR _and_ the new version is higher + // 2. Running nanoBooter and there is no version information on the CLR (presumably because there is no CLR installed) + if (fwPackage.Version > nanoDevice.CLRVersion) + { + bool attemptToLaunchBooter = false; - if (!attemptToLaunchBooter) + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) + { + // any update has to be handled by nanoBooter, so let's have it running + try { - // check for version where the software reboot to nanoBooter was made available - if (currentClrVersion != null && - nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) - { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Launching nanoBooter..."); - await Task.Yield(); + attemptToLaunchBooter = nanoDevice.ConnectToNanoBooter(); + + if (!attemptToLaunchBooter) + { + // check for version where the software reboot to nanoBooter was made available + if (currentClrVersion != null && + nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) + { + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); + + await Task.Yield(); + } } } + catch + { + // this reboot step can go wrong and there's no big deal with that + } } - catch + else { - // this reboot step can go wrong and there's no big deal with that + attemptToLaunchBooter = true; } - } - else - { - attemptToLaunchBooter = true; - } - // check if the device is still there - if(ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == deviceUniqueId) == null) - { + // check if the device is still there + if (ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == deviceUniqueId) == null) + { #if DEBUG - Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} is not available anymore."); + Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} is not available anymore."); #endif - return; - } - - if (attemptToLaunchBooter && - nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) - { - // get address for CLR block expected by device - var clrAddress = nanoDevice.GetCLRStartAddress(); - - // compare with address on the fw packages - if (clrAddress != - (fwPackage as Stm32Firmware).ClrStartAddress) - { - // CLR addresses don't match, can't proceed with update - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't update device. CLR addresses are different. Please update nanoBooter manually."); return; } - await Task.Yield(); + if (attemptToLaunchBooter && + nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) + { + // get address for CLR block expected by device + var clrAddress = nanoDevice.GetCLRStartAddress(); - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Starting update to CLR v{fwPackage.Version}."); + // compare with address on the fw packages + if (clrAddress != + (fwPackage as Stm32Firmware).ClrStartAddress) + { + // CLR addresses don't match, can't proceed with update + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't update device. CLR addresses are different. Please update nanoBooter manually."); + return; + } - try - { await Task.Yield(); - // create a progress indicator to be used by deployment operation to post debug messages - var progressIndicator = new Progress(m => MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] {m}")); + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Starting update to CLR v{fwPackage.Version}."); - if (nanoDevice.DeployBinaryFile( - (fwPackage as Stm32Firmware).nanoClrFileBin, - (fwPackage as Stm32Firmware).ClrStartAddress, - progressIndicator)) + try { await Task.Yield(); - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Update successful."); - - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); - } + // create a progress indicator to be used by deployment operation to post debug messages + var progressIndicator = new Progress(m => MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] {m}")); - // if this is the selected device... - if (ViewModelLocator.DeviceExplorer.SelectedDevice?.DeviceUniqueId == deviceUniqueId) - { - // ...reset property to force that device capabilities to be retrieved on next connection - ViewModelLocator.DeviceExplorer.LastDeviceConnectedHash = 0; - } + if (nanoDevice.DeployBinaryFile( + (fwPackage as Stm32Firmware).nanoClrFileBin, + (fwPackage as Stm32Firmware).ClrStartAddress, + progressIndicator)) + { + await Task.Yield(); - if (attemptToLaunchBooter) - { - // try to reboot target + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Update successful."); - // remove it from updatING list - devicesUpdatING.TryRemove(deviceId, out var dummy); + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + } - // check if the device is still there - if (ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == Guid.Parse(deviceId)) == null) + // if this is the selected device... + if (ViewModelLocator.DeviceExplorer.SelectedDevice?.DeviceUniqueId == deviceUniqueId) { - return; + // ...reset property to force that device capabilities to be retrieved on next connection + ViewModelLocator.DeviceExplorer.LastDeviceConnectedHash = 0; } - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Rebooting..."); + if (attemptToLaunchBooter) + { + // try to reboot target + + // remove it from updatING list + devicesUpdatING.TryRemove(deviceId, out var dummy); - nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); + // check if the device is still there + if (ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == Guid.Parse(deviceId)) == null) + { + return; + } + + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Rebooting..."); + + nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); + } + } + catch (Exception ex) + { + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); } } - catch (Exception ex) + else { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); + if (attemptToLaunchBooter) + { + // only report this as an error if the launch was successful + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Failed to launch nanoBooter. Quitting update."); + } } } else { - if (attemptToLaunchBooter) + // just to make sure that the CLR version is the latest, so we don't check it over and over + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR && + (fwPackage.Version == nanoDevice.DeviceInfo.ClrBuildVersion)) { - // only report this as an error if the launch was successful - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Failed to launch nanoBooter. Quitting update."); + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); } } } else { - // just to make sure that the CLR version is the latest, so we don't check it over and over - if (nanoDevice.DebugEngine.IsConnectedTonanoCLR && - (fwPackage.Version == nanoDevice.DeviceInfo.ClrBuildVersion)) - { - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); - } + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't connect to device. Quitting update."); } } - else + /////////////////////////////////// + // ESP32 targets + else if (fwPackage is Esp32Firmware) { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't connect to device. Quitting update."); + // TODO + // not supported yet + + MessageCentre.OutputFirmwareUpdateMessage("The ability to update ESP32 targets is not currently available. Yet..."); + + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); } - } - /////////////////////////////////// - // ESP32 targets - else if (fwPackage is Esp32Firmware) - { - // TODO - // not supported yet + /////////////////////////////////////// + // TI CC13x26x2 + else if (fwPackage is CC13x26x2Firmware) + { + // TODO + // not supported yet - MessageCentre.OutputFirmwareUpdateMessage("The ability to update ESP32 targets is not currently available. Yet..."); + MessageCentre.OutputFirmwareUpdateMessage("The ability to update CC13x26x2 targets is not currently available. Yet..."); - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + } + else + { + // shouldn't be here.... + } } - /////////////////////////////////////// - // TI CC13x26x2 - else if (fwPackage is CC13x26x2Firmware) + catch (Exception ex) { - // TODO - // not supported yet - - MessageCentre.OutputFirmwareUpdateMessage("The ability to update CC13x26x2 targets is not currently available. Yet..."); - - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); } - else + finally { - // shouldn't be here.... + // remove it from updatING list + devicesUpdatING.TryRemove(deviceId, out var dummy); } - } - catch (Exception ex) - { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); + } finally { - // remove it from updatING list - devicesUpdatING.TryRemove(deviceId, out var dummy); + nanoDevice.DebugEngine?.Stop(); + + exclusiveAccess?.Dispose(); } } }); } internal static async Task GetFirmwarePackageAsync( - string targetName, + string targetName, string platformName) { if (platformName.StartsWith("STM32")) diff --git a/VisualStudio.Extension-2022/AutomaticUpdates/UpdateManager.cs b/VisualStudio.Extension-2022/AutomaticUpdates/UpdateManager.cs index 8def9bc9..a670aebe 100644 --- a/VisualStudio.Extension-2022/AutomaticUpdates/UpdateManager.cs +++ b/VisualStudio.Extension-2022/AutomaticUpdates/UpdateManager.cs @@ -1,24 +1,24 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; using GalaSoft.MvvmLight.Messaging; using Microsoft.VisualStudio.Shell; using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.NFDevice; using nanoFramework.Tools.VisualStudio.Extension.FirmwareUpdate; using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Task = System.Threading.Tasks.Task; namespace nanoFramework.Tools.VisualStudio.Extension.AutomaticUpdates { public class UpdateManager { + private const int ExclusiveAccessTimeout = 3000; + private static UpdateManager s_instance; private ViewModelLocator ViewModelLocator; private readonly Package _package; @@ -32,7 +32,7 @@ private UpdateManager(Package package) } public static void Initialize( - AsyncPackage package, + AsyncPackage package, ViewModelLocator vmLocator) { s_instance = new UpdateManager(package) @@ -102,256 +102,278 @@ private void LaunchUpdate(string deviceId) } #endif - // check if DebugEngine is available - if (nanoDevice.DebugEngine == null) + GlobalExclusiveDeviceAccess exclusiveAccess = null; + try { - nanoDevice.CreateDebugEngine(); - } + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(nanoDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + // Can't get access, quit update for now +#if DEBUG + Console.WriteLine($"[Automatic Updates] Cannot access {nanoDevice.Description}, another application is using the device"); +#endif + return; + } - if (nanoDevice.DebugEngine == null) - { - // can't create it, quit update now - return; - } + // check if DebugEngine is available + if (nanoDevice.DebugEngine == null) + { + nanoDevice.CreateDebugEngine(); + } - // add this device to the updatING list - if (!devicesUpdatING.TryAdd(deviceId, new object())) - { - // fail to add device to list + if (nanoDevice.DebugEngine == null) + { + // can't create it, quit update now + return; + } + + // add this device to the updatING list + if (!devicesUpdatING.TryAdd(deviceId, new object())) + { + // fail to add device to list #if DEBUG - Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} update already in progress."); + Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} update already in progress."); #endif - // quit, never mind, this is not critical whatsoever - return; - } + // quit, never mind, this is not critical whatsoever + return; + } - // better wrap this on a try-finally because a lot of things can go wrong in the process - try - { - await Task.Yield(); + // better wrap this on a try-finally because a lot of things can go wrong in the process + try + { + await Task.Yield(); - var fwPackage = await GetFirmwarePackageAsync( - nanoDevice.TargetName, - nanoDevice.Platform); + var fwPackage = await GetFirmwarePackageAsync( + nanoDevice.TargetName, + nanoDevice.Platform); - await Task.Yield(); + await Task.Yield(); - ////////////////////////////// - // STM32 targets - if (fwPackage is Stm32Firmware) - { - // sanity check - if (nanoDevice.DebugEngine == null) + ////////////////////////////// + // STM32 targets + if (fwPackage is Stm32Firmware) { + // sanity check + if (nanoDevice.DebugEngine == null) + { #if DEBUG - Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} debug engine is not ready."); + Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} debug engine is not ready."); #endif - // quit - return; - } - - if (nanoDevice.DebugEngine.Connect( - 1000, - true, - true)) - { - Version currentClrVersion = null; - - // try to store CLR version - if(nanoDevice.DebugEngine.IsConnectedTonanoCLR) - { - if (nanoDevice.DeviceInfo.Valid) - { - currentClrVersion = nanoDevice.DeviceInfo.SolutionBuildVersion; - } + // quit + return; } - // update conditions: - // 1. Running CLR _and_ the new version is higher - // 2. Running nanoBooter and there is no version information on the CLR (presumably because there is no CLR installed) - if (fwPackage.Version > nanoDevice.CLRVersion) + if (nanoDevice.DebugEngine.Connect( + 1000, + true, + true)) { - bool attemptToLaunchBooter = false; + Version currentClrVersion = null; + // try to store CLR version if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) { - // any update has to be handled by nanoBooter, so let's have it running - try + if (nanoDevice.DeviceInfo.Valid) { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Launching nanoBooter..."); + currentClrVersion = nanoDevice.DeviceInfo.SolutionBuildVersion; + } + } - attemptToLaunchBooter = nanoDevice.ConnectToNanoBooter(); + // update conditions: + // 1. Running CLR _and_ the new version is higher + // 2. Running nanoBooter and there is no version information on the CLR (presumably because there is no CLR installed) + if (fwPackage.Version > nanoDevice.CLRVersion) + { + bool attemptToLaunchBooter = false; - if (!attemptToLaunchBooter) + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR) + { + // any update has to be handled by nanoBooter, so let's have it running + try { - // check for version where the software reboot to nanoBooter was made available - if (currentClrVersion != null && - nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) - { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Launching nanoBooter..."); - await Task.Yield(); + attemptToLaunchBooter = nanoDevice.ConnectToNanoBooter(); + + if (!attemptToLaunchBooter) + { + // check for version where the software reboot to nanoBooter was made available + if (currentClrVersion != null && + nanoDevice.DeviceInfo.SolutionBuildVersion < new Version("1.6.0.54")) + { + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] The device is running a version that doesn't support rebooting by software. Please update your device using 'nanoff' tool."); + + await Task.Yield(); + } } } + catch + { + // this reboot step can go wrong and there's no big deal with that + } } - catch + else { - // this reboot step can go wrong and there's no big deal with that + attemptToLaunchBooter = true; } - } - else - { - attemptToLaunchBooter = true; - } - // check if the device is still there - if(ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == deviceUniqueId) == null) - { + // check if the device is still there + if (ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == deviceUniqueId) == null) + { #if DEBUG - Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} is not available anymore."); + Console.WriteLine($"[Automatic Updates] {nanoDevice.TargetName} is not available anymore."); #endif - return; - } - - if (attemptToLaunchBooter && - nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) - { - // get address for CLR block expected by device - var clrAddress = nanoDevice.GetCLRStartAddress(); - - // compare with address on the fw packages - if (clrAddress != - (fwPackage as Stm32Firmware).ClrStartAddress) - { - // CLR addresses don't match, can't proceed with update - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't update device. CLR addresses are different. Please update nanoBooter manually."); return; } - await Task.Yield(); + if (attemptToLaunchBooter && + nanoDevice.Ping() == Debugger.WireProtocol.ConnectionSource.nanoBooter) + { + // get address for CLR block expected by device + var clrAddress = nanoDevice.GetCLRStartAddress(); - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Starting update to CLR v{fwPackage.Version}."); + // compare with address on the fw packages + if (clrAddress != + (fwPackage as Stm32Firmware).ClrStartAddress) + { + // CLR addresses don't match, can't proceed with update + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't update device. CLR addresses are different. Please update nanoBooter manually."); + return; + } - try - { await Task.Yield(); - // create a progress indicator to be used by deployment operation to post debug messages - var progressIndicator = new Progress(m => MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] {m}")); + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Starting update to CLR v{fwPackage.Version}."); - if (nanoDevice.DeployBinaryFile( - (fwPackage as Stm32Firmware).nanoClrFileBin, - (fwPackage as Stm32Firmware).ClrStartAddress, - progressIndicator)) + try { await Task.Yield(); - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Update successful."); - - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); - } + // create a progress indicator to be used by deployment operation to post debug messages + var progressIndicator = new Progress(m => MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] {m}")); - // if this is the selected device... - if (ViewModelLocator.DeviceExplorer.SelectedDevice?.DeviceUniqueId == deviceUniqueId) - { - // ...reset property to force that device capabilities to be retrieved on next connection - ViewModelLocator.DeviceExplorer.LastDeviceConnectedHash = 0; - } + if (nanoDevice.DeployBinaryFile( + (fwPackage as Stm32Firmware).nanoClrFileBin, + (fwPackage as Stm32Firmware).ClrStartAddress, + progressIndicator)) + { + await Task.Yield(); - if (attemptToLaunchBooter) - { - // try to reboot target + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Update successful."); - // remove it from updatING list - devicesUpdatING.TryRemove(deviceId, out var dummy); + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + } - // check if the device is still there - if (ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == Guid.Parse(deviceId)) == null) + // if this is the selected device... + if (ViewModelLocator.DeviceExplorer.SelectedDevice?.DeviceUniqueId == deviceUniqueId) { - return; + // ...reset property to force that device capabilities to be retrieved on next connection + ViewModelLocator.DeviceExplorer.LastDeviceConnectedHash = 0; } - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Rebooting..."); + if (attemptToLaunchBooter) + { + // try to reboot target + + // remove it from updatING list + devicesUpdatING.TryRemove(deviceId, out var dummy); - nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); + // check if the device is still there + if (ViewModelLocator.DeviceExplorer.AvailableDevices.FirstOrDefault(d => d.DeviceUniqueId == Guid.Parse(deviceId)) == null) + { + return; + } + + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] Rebooting..."); + + nanoDevice.DebugEngine.RebootDevice(RebootOptions.NormalReboot); + } + } + catch (Exception ex) + { + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); } } - catch (Exception ex) + else { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); + if (attemptToLaunchBooter) + { + // only report this as an error if the launch was successful + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Failed to launch nanoBooter. Quitting update."); + } } } else { - if (attemptToLaunchBooter) + // just to make sure that the CLR version is the latest, so we don't check it over and over + if (nanoDevice.DebugEngine.IsConnectedTonanoCLR && + (fwPackage.Version == nanoDevice.DeviceInfo.ClrBuildVersion)) { - // only report this as an error if the launch was successful - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Failed to launch nanoBooter. Quitting update."); + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); } } } else { - // just to make sure that the CLR version is the latest, so we don't check it over and over - if (nanoDevice.DebugEngine.IsConnectedTonanoCLR && - (fwPackage.Version == nanoDevice.DeviceInfo.ClrBuildVersion)) - { - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); - } + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't connect to device. Quitting update."); } } - else + /////////////////////////////////// + // ESP32 targets + else if (fwPackage is Esp32Firmware) { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Can't connect to device. Quitting update."); + // TODO + // not supported yet + + MessageCentre.OutputFirmwareUpdateMessage("The ability to update ESP32 targets is not currently available. Yet..."); + + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); } - } - /////////////////////////////////// - // ESP32 targets - else if (fwPackage is Esp32Firmware) - { - // TODO - // not supported yet + /////////////////////////////////////// + // TI CC13x26x2 + else if (fwPackage is CC13x26x2Firmware) + { + // TODO + // not supported yet - MessageCentre.OutputFirmwareUpdateMessage("The ability to update ESP32 targets is not currently available. Yet..."); + MessageCentre.OutputFirmwareUpdateMessage("The ability to update CC13x26x2 targets is not currently available. Yet..."); - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + // add it to the list of devices updatED with the update time stamp + devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + } + else + { + // shouldn't be here.... + } } - /////////////////////////////////////// - // TI CC13x26x2 - else if (fwPackage is CC13x26x2Firmware) + catch (Exception ex) { - // TODO - // not supported yet - - MessageCentre.OutputFirmwareUpdateMessage("The ability to update CC13x26x2 targets is not currently available. Yet..."); - - // add it to the list of devices updatED with the update time stamp - devicesUpdatED.TryAdd(deviceDescription, DateTime.UtcNow); + MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); } - else + finally { - // shouldn't be here.... + // remove it from updatING list + devicesUpdatING.TryRemove(deviceId, out var dummy); } - } - catch (Exception ex) - { - MessageCentre.OutputFirmwareUpdateMessage($"[{deviceDescription}] ERROR: Exception occurred when performing update ({ex.Message})."); + } finally { - // remove it from updatING list - devicesUpdatING.TryRemove(deviceId, out var dummy); + nanoDevice.DebugEngine?.Stop(); + + exclusiveAccess?.Dispose(); } } }); } internal static async Task GetFirmwarePackageAsync( - string targetName, + string targetName, string platformName) { if (platformName.StartsWith("STM32")) diff --git a/VisualStudio.Extension-2022/NanoFrameworkMoniker.imagemanifest b/VisualStudio.Extension-2022/NanoFrameworkMoniker.imagemanifest index e6d50d82..526fd6ab 100644 --- a/VisualStudio.Extension-2022/NanoFrameworkMoniker.imagemanifest +++ b/VisualStudio.Extension-2022/NanoFrameworkMoniker.imagemanifest @@ -1,7 +1,7 @@  - + diff --git a/spelling_exclusion.dic b/spelling_exclusion.dic new file mode 100644 index 00000000..1b759ec4 --- /dev/null +++ b/spelling_exclusion.dic @@ -0,0 +1,2 @@ +nano + diff --git a/vs-extension.shared/CorDebug/CorDebugProcess.cs b/vs-extension.shared/CorDebug/CorDebugProcess.cs index b60f7058..2fb097b9 100644 --- a/vs-extension.shared/CorDebug/CorDebugProcess.cs +++ b/vs-extension.shared/CorDebug/CorDebugProcess.cs @@ -1,20 +1,17 @@ -// -// Copyright (c) .NET Foundation and Contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using CorDebugInterop; -using Microsoft.VisualStudio.Debugger.Interop; -using nanoFramework.Tools.Debugger; -using nanoFramework.Tools.Debugger.Extensions; -using nanoFramework.Tools.Debugger.WireProtocol; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; +using CorDebugInterop; +using Microsoft.VisualStudio.Debugger.Interop; +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.Extensions; +using nanoFramework.Tools.Debugger.WireProtocol; using BreakpointDef = nanoFramework.Tools.Debugger.WireProtocol.Commands.Debugging_Execution_BreakpointDef; namespace nanoFramework.Tools.VisualStudio.Extension @@ -79,15 +76,15 @@ public CorDebugProcess(DebugPort debugPort, NanoDeviceBase nanoDevice) _syncTerminatingObject = new object(); } - public void Dispose() + public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); + Dispose(true); + GC.SuppressFinalize(this); } bool m_fDisposed = false; - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { if (!m_fDisposed) { @@ -97,14 +94,14 @@ protected virtual void Dispose(bool disposing) } // free "unmanaged" stuff - StopDebugging(); + StopDebugging(); m_fDisposed = true; } } ~CorDebugProcess() { - Dispose (false); + Dispose(false); } @@ -126,11 +123,11 @@ public void SetPid(uint pid) public bool IsDebugging => _corDebug != null; - public void SetCurrentAppDomain( CorDebugAppDomain appDomain ) + public void SetCurrentAppDomain(CorDebugAppDomain appDomain) { - if(appDomain != _appDomainCurrent) + if (appDomain != _appDomainCurrent) { - if(appDomain != null && Engine.Capabilities.AppDomains) + if (appDomain != null && Engine.Capabilities.AppDomains) { Engine.SetCurrentAppDomain(appDomain.Id); } @@ -161,7 +158,7 @@ private void OnProcessExit(object sender, EventArgs args) // catch all as this can throw and we need to continue } - if(!isProcess) + if (!isProcess) { // try sender as Engine try @@ -355,7 +352,7 @@ private void EnsureProcessIsInInitializedState() } else { - MessageCentre.InternalErrorWriteLine($"Device is running nanoCLR, requesting a restart and pause of debugger ({retry + 1}/{ maxOperationRetries })."); + MessageCentre.InternalErrorWriteLine($"Device is running nanoCLR, requesting a restart and pause of debugger ({retry + 1}/{maxOperationRetries})."); bool rebootSuccessful = _engine.RebootDevice(RebootOptions.ClrOnly | RebootOptions.WaitForDebugger); @@ -373,7 +370,7 @@ private void EnsureProcessIsInInitializedState() } else if (_engine.IsConnectedTonanoBooter) { - MessageCentre.InternalErrorWriteLine($"Device is running nanoBooter, requesting to launch nanoCLR ({retry + 1}/{ maxOperationRetries })."); + MessageCentre.InternalErrorWriteLine($"Device is running nanoBooter, requesting to launch nanoCLR ({retry + 1}/{maxOperationRetries})."); // this is telling nanoBooter to enter CLR _engine.ExecuteMemory(0); @@ -416,7 +413,7 @@ private void EnsureProcessIsInInitializedState() public Engine AttachToEngine() { - for(int retry = 0; retry < maxOperationRetries; retry++) + for (int retry = 0; retry < maxOperationRetries; retry++) { if (ShuttingDown) { @@ -425,7 +422,7 @@ public Engine AttachToEngine() break; } - MessageCentre.InternalErrorWriteLine($"Attempting to connect the debugger engine ({retry + 1}/{ maxOperationRetries })"); + MessageCentre.InternalErrorWriteLine($"Attempting to connect the debugger engine ({retry + 1}/{maxOperationRetries})"); try { @@ -497,7 +494,7 @@ public Engine AttachToEngine() var executionMode = Commands.DebuggingExecutionChangeConditions.State.SourceLevelDebugging; // check if we need to disable the stack trace in exceptions - if(_engine.ThrowOnCommunicationFailure) + if (_engine.ThrowOnCommunicationFailure) { executionMode |= Commands.DebuggingExecutionChangeConditions.State.NoStackTraceInExceptions; } @@ -517,14 +514,14 @@ public Engine AttachToEngine() { DetachFromEngine(); - if(!ShuttingDown) + if (!ShuttingDown) { Thread.Sleep(10); } } } - if(_engine != null && !_engine.IsConnected) + if (_engine != null && !_engine.IsConnected) { DetachFromEngine(); @@ -555,21 +552,21 @@ private bool FlushEvent() bool fContinue = false; ManagedCallbacks.ManagedCallback mc = null; - lock(this) + lock (this) { - if(_cStopped == 0 && AnyQueuedEvents) + if (_cStopped == 0 && AnyQueuedEvents) { - Interlocked.Increment( ref _cStopped ); + Interlocked.Increment(ref _cStopped); mc = (ManagedCallbacks.ManagedCallback)_events.Dequeue(); } } - if(mc != null) + if (mc != null) { DebugAssert(ShuttingDown || IsExecutionPaused || mc is ManagedCallbacks.ManagedCallbackDebugMessage, "Error on FlushEvent"); - mc.Dispatch( _corDebug.ManagedCallback ); + mc.Dispatch(_corDebug.ManagedCallback); fContinue = true; } @@ -685,7 +682,7 @@ private void StartClr() try { EnqueueStartupEventsAndWait(); - + MessageCentre.DebugMessage(Resources.ResourceStrings.AttachingToDevice); if (AttachToEngine() == null) @@ -699,7 +696,7 @@ private void StartClr() if (_fLaunched) { //This will reboot the device if start debugging was done without a deployment - + MessageCentre.DebugMessage(Resources.ResourceStrings.WaitingDeviceInitialization); EnsureProcessIsInInitializedState(); } @@ -725,7 +722,7 @@ private void StartClr() } } } - catch(Exception ex) + catch (Exception ex) { MessageCentre.DebugMessage(Resources.ResourceStrings.InitializationFailed); @@ -737,15 +734,15 @@ private void StartClr() private void UpdateThreadList() { - /* - This is a bit of a hack (or performance improvement, if you prefer) - The nanoCLR creates threads with wild abandon, but ICorDebug specifies that - thread creation/destruction events should stop the CLR, and provide callbacks - This can slow down debugging anything that makes heavy use of threads - For example...managed drivers, timers, etc... - So we are faking the events just in time, in a couple of cases -- - when a real breakpoint gets hit, when execution is stopped via BreakAll, etc.. - */ + /* + This is a bit of a hack (or performance improvement, if you prefer) + The nanoCLR creates threads with wild abandon, but ICorDebug specifies that + thread creation/destruction events should stop the CLR, and provide callbacks + This can slow down debugging anything that makes heavy use of threads + For example...managed drivers, timers, etc... + So we are faking the events just in time, in a couple of cases -- + when a real breakpoint gets hit, when execution is stopped via BreakAll, etc.. + */ MessageCentre.InternalErrorWriteLine(Resources.ResourceStrings.RunningThreadsInformation); @@ -789,7 +786,7 @@ public void StartDebugging(CorDebug corDebug, bool fLaunch) Init(corDebug, fLaunch); - _threadDispatch = new Thread(delegate() + _threadDispatch = new Thread(delegate () { try { @@ -842,7 +839,7 @@ public AD_PROCESS_ID PhysicalProcessId { AD_PROCESS_ID id = new AD_PROCESS_ID(); - id.ProcessIdType = (uint) AD_PROCESS_ID_TYPE.AD_PROCESS_ID_SYSTEM; + id.ProcessIdType = (uint)AD_PROCESS_ID_TYPE.AD_PROCESS_ID_SYSTEM; id.dwProcessId = _pid; return id; } @@ -875,7 +872,7 @@ private void ReallocScratchPad(int size) if (wr != null) { - CorDebugValue val = (CorDebugValue) wr.Target; + CorDebugValue val = (CorDebugValue)wr.Target; if (val != null) { @@ -945,7 +942,7 @@ public CorDebugValue GetValue(int index, CorDebugAppDomain appDomain) m_scratchPad[index] = wr; } - CorDebugValue val = (CorDebugValue) wr.Target; + CorDebugValue val = (CorDebugValue)wr.Target; if (val == null) { @@ -1081,12 +1078,12 @@ private void ResumeExecution() public CorDebugAssembly AssemblyFromIdx(uint idx) { - return CorDebugAssembly.AssemblyFromIdx( idx, _assemblies ); + return CorDebugAssembly.AssemblyFromIdx(idx, _assemblies); } public CorDebugAssembly AssemblyFromIndex(uint index) { - return CorDebugAssembly.AssemblyFromIndex( index, _assemblies ); + return CorDebugAssembly.AssemblyFromIndex(index, _assemblies); } public void RegisterBreakpoint(object o, bool fRegister) @@ -1103,17 +1100,17 @@ public void RegisterBreakpoint(object o, bool fRegister) DirtyBreakpoints(); } - internal ArrayList GetBreakpoints( Type t, CorDebugAppDomain appDomain ) + internal ArrayList GetBreakpoints(Type t, CorDebugAppDomain appDomain) { - ArrayList al = new ArrayList( _breakpoints.Count ); + ArrayList al = new ArrayList(_breakpoints.Count); - foreach(CorDebugBreakpointBase breakpoint in _breakpoints) + foreach (CorDebugBreakpointBase breakpoint in _breakpoints) { - if(t.IsAssignableFrom( breakpoint.GetType() )) + if (t.IsAssignableFrom(breakpoint.GetType())) { - if(appDomain == null || appDomain == breakpoint.AppDomain) + if (appDomain == null || appDomain == breakpoint.AppDomain) { - al.Add( breakpoint ); + al.Add(breakpoint); } } } @@ -1121,22 +1118,22 @@ internal ArrayList GetBreakpoints( Type t, CorDebugAppDomain appDomain ) return al; } - private CorDebugBreakpointBase[] FindBreakpoints( BreakpointDef breakpointDef ) + private CorDebugBreakpointBase[] FindBreakpoints(BreakpointDef breakpointDef) { //perhaps need to cheat for CorDebugEval.Breakpoint for uncaught exceptions... - ArrayList breakpoints = new ArrayList( 1 ); - if( IsDebugging ) + ArrayList breakpoints = new ArrayList(1); + if (IsDebugging) { - foreach( CorDebugBreakpointBase breakpoint in _breakpoints ) + foreach (CorDebugBreakpointBase breakpoint in _breakpoints) { - if( breakpoint.IsMatch( breakpointDef ) ) + if (breakpoint.IsMatch(breakpointDef)) { - breakpoints.Add( breakpoint ); + breakpoints.Add(breakpoint); } } } - return (CorDebugBreakpointBase[])breakpoints.ToArray( typeof( CorDebugBreakpointBase ) ); + return (CorDebugBreakpointBase[])breakpoints.ToArray(typeof(CorDebugBreakpointBase)); } private bool BreakpointHit(BreakpointDef breakpointDef) @@ -1147,23 +1144,23 @@ private bool BreakpointHit(BreakpointDef breakpointDef) CorDebugBreakpointBase[] breakpoints = FindBreakpoints(breakpointDef); bool fStopExecution = false; - for(int iBreakpoint = 0; iBreakpoint < breakpoints.Length; iBreakpoint++) + for (int iBreakpoint = 0; iBreakpoint < breakpoints.Length; iBreakpoint++) { CorDebugBreakpointBase breakpoint = breakpoints[iBreakpoint]; - if(breakpoint.ShouldBreak(breakpointDef)) + if (breakpoint.ShouldBreak(breakpointDef)) { fStopExecution = true; break; } } - if(fStopExecution) + if (fStopExecution) { - for(int iBreakpoint = 0; iBreakpoint < breakpoints.Length; iBreakpoint++) + for (int iBreakpoint = 0; iBreakpoint < breakpoints.Length; iBreakpoint++) { CorDebugBreakpointBase breakpoint = breakpoints[iBreakpoint]; - breakpoint.Hit( breakpointDef ); + breakpoint.Hit(breakpointDef); } } @@ -1172,36 +1169,36 @@ private bool BreakpointHit(BreakpointDef breakpointDef) public void UpdateBreakpoints() { - if(!IsAttachedToEngine || !_fUpdateBreakpoints || ShuttingDown) + if (!IsAttachedToEngine || !_fUpdateBreakpoints || ShuttingDown) return; //Function breakpoints are set for each AppDomain. //No need to send all duplicates to the nanoCLR - ArrayList al = new ArrayList( _breakpoints.Count ); - for(int i = 0; i < _breakpoints.Count; i++) + ArrayList al = new ArrayList(_breakpoints.Count); + for (int i = 0; i < _breakpoints.Count; i++) { CorDebugBreakpointBase breakpoint1 = ((CorDebugBreakpointBase)_breakpoints[i]); bool fDuplicate = false; - for(int j = 0; j < i; j++) + for (int j = 0; j < i; j++) { CorDebugBreakpointBase breakpoint2 = ((CorDebugBreakpointBase)_breakpoints[j]); - if(breakpoint1.Equals( breakpoint2 )) + if (breakpoint1.Equals(breakpoint2)) { fDuplicate = true; break; } } - if(!fDuplicate) + if (!fDuplicate) { - al.Add( breakpoint1.Debugging_Execution_BreakpointDef ); + al.Add(breakpoint1.Debugging_Execution_BreakpointDef); } } - BreakpointDef[] breakpointDefs = (BreakpointDef[])al.ToArray( typeof( BreakpointDef ) ); + BreakpointDef[] breakpointDefs = (BreakpointDef[])al.ToArray(typeof(BreakpointDef)); Engine.SetBreakpoints(breakpointDefs); _fUpdateBreakpoints = false; @@ -1320,7 +1317,7 @@ private void WaitDummyThread() // Utility.Kernel32.DuplicateHandle(Utility.Kernel32.GetCurrentProcess(), emuProcess.Handle, // Utility.Kernel32.GetCurrentProcess(), out lpProcessInformation.hProcess, // 0, false, DUPLICATE_SAME_ACCESS); - + // lpProcessInformation.dwProcessId = (uint)emuProcess.Id; // CreateDummyThread(out lpProcessInformation.hThread, out lpProcessInformation.dwThreadId); // } @@ -1347,18 +1344,18 @@ private void CreateDeviceProcess(DebugPort port, string lpApplicationName, strin } private void InternalCreateProcess( - DebugPort port, - string lpApplicationName, - string lpCommandLine, - IntPtr lpProcessAttributes, - IntPtr lpThreadAttributes, - int bInheritHandles, - uint dwCreationFlags, + DebugPort port, + string lpApplicationName, + string lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + int bInheritHandles, + uint dwCreationFlags, System.IntPtr lpEnvironment, - string lpCurrentDirectory, + string lpCurrentDirectory, ref _STARTUPINFO lpStartupInfo, ref _PROCESS_INFORMATION lpProcessInformation, - uint debuggingFlags + uint debuggingFlags ) { //if (port.IsLocalPort) @@ -1375,7 +1372,7 @@ public static CorDebugProcess CreateProcessEx(IDebugPort2 pPort, string lpApplic CommandLineBuilder cb = new CommandLineBuilder(lpCommandLine); string[] args = cb.Arguments; - string deployDeviceName = args[args.Length-1]; + string deployDeviceName = args[args.Length - 1]; //Extract deployDeviceName if (!deployDeviceName.StartsWith(CorDebugProcess.DeployDeviceName)) @@ -1397,7 +1394,7 @@ public static CorDebugProcess CreateProcessEx(IDebugPort2 pPort, string lpApplic return process; } - internal ulong FakeLoadAssemblyIntoMemory( CorDebugAssembly assembly ) + internal ulong FakeLoadAssemblyIntoMemory(CorDebugAssembly assembly) { ulong address = _fakeAssemblyAddressNext; @@ -1420,7 +1417,7 @@ private void LoadAssemblies() DebugAssert(assemblies.Count > 0, "Error loading assemblies. Assemblies count is 0."); - if(assemblies.Count == 0) + if (assemblies.Count == 0) { // if debug was started, presumably after a successful deployment, there have to be assemblies on the device // so, if there are none, probably the command above failed, anyway we can't proceed with debugging @@ -1480,11 +1477,11 @@ private void LoadAssemblies() } } - public CorDebugAppDomain GetAppDomainFromId( uint id ) + public CorDebugAppDomain GetAppDomainFromId(uint id) { - foreach(CorDebugAppDomain appDomain in _appDomains) + foreach (CorDebugAppDomain appDomain in _appDomains) { - if(appDomain.Id == id) + if (appDomain.Id == id) { return appDomain; } @@ -1565,10 +1562,16 @@ private void StopDebugging() { _engine.ThrowOnCommunicationFailure = false; - // need to reboot device to clear memory leaks which are caused by the running app stopping execution and leaving C/C++ vars orphaned in the CRT heap - _engine.RebootDevice(RebootOptions.NormalReboot); - - DetachFromEngine(); + try + { + // need to reboot device to clear memory leaks which are caused by the running app stopping execution and leaving C/C++ vars orphaned in the CRT heap + _engine.RebootDevice(RebootOptions.NormalReboot); + } + finally + { + // Make sure the engine is detached (disposes the global exclusive access) + DetachFromEngine(); + } } } catch (Exception ex) @@ -1585,9 +1588,9 @@ public void OnMessage(IncomingMessage msg, string text) { if (_threads != null && _threads.Count > 0) { - if(_appDomains != null && _appDomains.Count > 0) + if (_appDomains != null && _appDomains.Count > 0) { - EnqueueEvent( new ManagedCallbacks.ManagedCallbackDebugMessage( (CorDebugThread)_threads[0], (CorDebugAppDomain)_appDomains[0], "nanoCLR_Message", text, LoggingLevelEnum.LStatusLevel0 ) ); + EnqueueEvent(new ManagedCallbacks.ManagedCallbackDebugMessage((CorDebugThread)_threads[0], (CorDebugAppDomain)_appDomains[0], "nanoCLR_Message", text, LoggingLevelEnum.LStatusLevel0)); } } else @@ -1693,9 +1696,9 @@ public void OnCommand(IncomingMessage msg, bool fReply) public void OnNoise(byte[] buf, int offset, int count) { - if(buf != null && (offset + count) <= buf.Length) + if (buf != null && (offset + count) <= buf.Length) { - MessageCentre.InternalErrorWriteLine( System.Text.UTF8Encoding.UTF8.GetString(buf, offset, count) ); + MessageCentre.InternalErrorWriteLine(System.Text.UTF8Encoding.UTF8.GetString(buf, offset, count)); } } @@ -1718,21 +1721,21 @@ public class BuiltinType private uint m_tkCLR; private CorDebugClass m_class; - public BuiltinType( CorDebugAssembly assembly, uint tkCLR, CorDebugClass cls ) + public BuiltinType(CorDebugAssembly assembly, uint tkCLR, CorDebugClass cls) { m_assembly = assembly; m_tkCLR = tkCLR; m_class = cls; } - public CorDebugAssembly GetAssembly( CorDebugAppDomain appDomain ) + public CorDebugAssembly GetAssembly(CorDebugAppDomain appDomain) { - return appDomain.AssemblyFromIdx( m_assembly.Idx ); + return appDomain.AssemblyFromIdx(m_assembly.Idx); } - public CorDebugClass GetClass( CorDebugAppDomain appDomain ) + public CorDebugClass GetClass(CorDebugAppDomain appDomain) { - CorDebugAssembly assembly = GetAssembly( appDomain ); + CorDebugAssembly assembly = GetAssembly(appDomain); return assembly.GetClassFromTokenCLR(m_tkCLR); } @@ -1748,7 +1751,7 @@ private void AddBuiltInType(object o, CorDebugAssembly assm, string type) uint tkCLR = MetaData.Helper.ClassTokenFromName(assm.MetaDataImport, type); CorDebugClass c = assm.GetClassFromTokenCLR(tkCLR); - BuiltinType builtInType = new BuiltinType( assm, tkCLR, c ); + BuiltinType builtInType = new BuiltinType(assm, tkCLR, c); _tdBuiltin[o] = builtInType; } @@ -1775,7 +1778,7 @@ public BuiltinType ResolveBuiltInType(object o) DebugAssert(assmCorLib != null, "Error resolving built-in type. Couldn't find mscorlib"); AddBuiltInType(CorElementType.ELEMENT_TYPE_BOOLEAN, assmCorLib, "System.Boolean"); - AddBuiltInType(CorElementType.ELEMENT_TYPE_CHAR, assmCorLib, "System.Char"); + AddBuiltInType(CorElementType.ELEMENT_TYPE_CHAR, assmCorLib, "System.Char"); AddBuiltInType(CorElementType.ELEMENT_TYPE_I1, assmCorLib, "System.SByte"); AddBuiltInType(CorElementType.ELEMENT_TYPE_U1, assmCorLib, "System.Byte"); AddBuiltInType(CorElementType.ELEMENT_TYPE_I2, assmCorLib, "System.Int16"); @@ -1798,7 +1801,7 @@ public BuiltinType ResolveBuiltInType(object o) AddBuiltInType(ReflectionDefinition.Kind.REFLECTION_METHOD, assmCorLib, "System.Reflection.RuntimeMethodInfo"); AddBuiltInType(ReflectionDefinition.Kind.REFLECTION_CONSTRUCTOR, assmCorLib, "System.Reflection.RuntimeConstructorInfo"); - AddBuiltInType(nanoClrDataType.DATATYPE_TRANSPARENT_PROXY, assmCorLib, "System.Runtime.Remoting.Proxies.__TransparentProxy" ); + AddBuiltInType(nanoClrDataType.DATATYPE_TRANSPARENT_PROXY, assmCorLib, "System.Runtime.Remoting.Proxies.__TransparentProxy"); } return (BuiltinType)_tdBuiltin[o]; @@ -2177,7 +2180,7 @@ int ICorDebugProcess.GetHelperThreadID(out uint pThreadID) #region ICorDebugProcess2 Members - int ICorDebugProcess2.GetVersion( out _COR_VERSION version ) + int ICorDebugProcess2.GetVersion(out _COR_VERSION version) { version = new _COR_VERSION(); version.dwMajor = 1; //This is needed to handle v1 exceptions. @@ -2185,33 +2188,33 @@ int ICorDebugProcess2.GetVersion( out _COR_VERSION version ) return COM_HResults.S_OK; } - int ICorDebugProcess2.GetThreadForTaskID( ulong taskid, out ICorDebugThread2 ppThread ) + int ICorDebugProcess2.GetThreadForTaskID(ulong taskid, out ICorDebugThread2 ppThread) { ppThread = null; return COM_HResults.E_NOTIMPL; } - int ICorDebugProcess2.SetUnmanagedBreakpoint( ulong address, uint bufsize, byte[] buffer, out uint bufLen ) + int ICorDebugProcess2.SetUnmanagedBreakpoint(ulong address, uint bufsize, byte[] buffer, out uint bufLen) { bufLen = 0; return COM_HResults.E_NOTIMPL; } - int ICorDebugProcess2.GetDesiredNGENCompilerFlags( out uint pdwFlags ) + int ICorDebugProcess2.GetDesiredNGENCompilerFlags(out uint pdwFlags) { pdwFlags = 0; return COM_HResults.E_NOTIMPL; } - int ICorDebugProcess2.SetDesiredNGENCompilerFlags( uint pdwFlags ) + int ICorDebugProcess2.SetDesiredNGENCompilerFlags(uint pdwFlags) { return COM_HResults.E_NOTIMPL; } - int ICorDebugProcess2.ClearUnmanagedBreakpoint( ulong address ) + int ICorDebugProcess2.ClearUnmanagedBreakpoint(ulong address) { return COM_HResults.E_NOTIMPL; } @@ -2277,17 +2280,17 @@ int Microsoft.VisualStudio.Debugger.Interop.IDebugProcess2.EnumPrograms(out IEnu { ArrayList appDomains = _appDomains; - if(!IsAttachedToEngine) + if (!IsAttachedToEngine) { //need to fake this in order to get the Attach Dialog to work. - DebugAssert( appDomains == null, "Error enumerating programs. AppDomain is null."); + DebugAssert(appDomains == null, "Error enumerating programs. AppDomain is null."); appDomains = new ArrayList { new CorDebugAppDomain(this, 1) }; } - ppEnum = new CorDebugEnum( appDomains, typeof( IDebugProgram2 ), typeof( IEnumDebugPrograms2 ) ); + ppEnum = new CorDebugEnum(appDomains, typeof(IDebugProgram2), typeof(IEnumDebugPrograms2)); return COM_HResults.S_OK; } @@ -2330,7 +2333,7 @@ int Microsoft.VisualStudio.Debugger.Interop.IDebugProcess2.GetInfo(enum_PROCESS_ if ((Fields & enum_PROCESS_INFO_FIELDS.PIF_FLAGS) != 0) { - if(_executionPaused) + if (_executionPaused) pi.Flags = enum_PROCESS_INFO_FLAGS.PIFLAG_PROCESS_STOPPED; else pi.Flags = enum_PROCESS_INFO_FLAGS.PIFLAG_PROCESS_RUNNING; @@ -2417,23 +2420,23 @@ private static void DebugAssert(bool condition, string message) private string GetCommandName(uint cmd) { - switch(cmd) + switch (cmd) { case Commands.c_Debugging_Execution_BreakpointStatus: return "BreakpointStatus"; case Commands.c_Debugging_Execution_QueryCLRCapabilities: return "QueryCLRCapabilities"; - + case Commands.c_Debugging_Execution_Allocate: return "Allocate"; - + case Commands.c_Debugging_Execution_ChangeConditions: return "ChangeConditions"; default: return $"0x{cmd:X8}"; - } + } } } } diff --git a/vs-extension.shared/DebugLauncher/NanoDebuggerLaunchProvider.cs b/vs-extension.shared/DebugLauncher/NanoDebuggerLaunchProvider.cs index abc10cb0..527f6e26 100644 --- a/vs-extension.shared/DebugLauncher/NanoDebuggerLaunchProvider.cs +++ b/vs-extension.shared/DebugLauncher/NanoDebuggerLaunchProvider.cs @@ -1,20 +1,19 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using GalaSoft.MvvmLight.Ioc; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Debug; -using Microsoft.VisualStudio.ProjectSystem.VS.Debug; -using Microsoft.VisualStudio.Threading; -using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using GalaSoft.MvvmLight.Ioc; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Debug; +using Microsoft.VisualStudio.ProjectSystem.VS.Debug; +using Microsoft.VisualStudio.Threading; +using nanoFramework.Tools.Debugger.NFDevice; +using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; namespace nanoFramework.Tools.VisualStudio.Extension { @@ -22,6 +21,8 @@ namespace nanoFramework.Tools.VisualStudio.Extension [AppliesTo(NanoCSharpProjectUnconfigured.UniqueCapability)] internal partial class NanoDebuggerLaunchProvider : DebugLaunchProviderBase { + private const int ExclusiveAccessTimeout = 3000; + private static AssemblyInformationalVersionAttribute _informationalVersionAttribute; [ImportingConstructor] @@ -50,35 +51,62 @@ public override async Task> QueryDebugTarget // get device var device = SimpleIoc.Default.GetInstance().SelectedDevice; - // check for debug engine - if (device.DebugEngine == null) + var exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(device, ExclusiveAccessTimeout); + if (exclusiveAccess is null) { - device.CreateDebugEngine(); +#pragma warning disable S112 // OK to use Exception here + throw new Exception($"Can't get access to {deployDeviceName}, another application is using the device!"); +#pragma warning restore S112 // General exceptions should never be thrown } - - // update stack trace processing option - device.DebugEngine.NoStackTraceInExceptions = !NanoFrameworkPackage.DebuggingOptions.ProcessStackTraceOption; - - // make sure that the device is connected - if (device.DebugEngine.Connect( - false, - true)) + else { - string commandLine = await GetCommandLineForLaunchAsync(); - commandLine = string.Format("{0} \"{1}{2}\"", commandLine, CorDebugProcess.DeployDeviceName, deployDeviceName); + var stopDebugEngine = true; - var settings = new DebugLaunchSettings(launchOptions) + try + { + // check for debug engine + if (device.DebugEngine == null) + { + device.CreateDebugEngine(); + } + + // update stack trace processing option + device.DebugEngine.NoStackTraceInExceptions = !NanoFrameworkPackage.DebuggingOptions.ProcessStackTraceOption; + + // make sure that the device is connected + if (device.DebugEngine.Connect( + false, + true)) + { + string commandLine = await GetCommandLineForLaunchAsync(); + commandLine = string.Format("{0} \"{1}{2}\"", commandLine, CorDebugProcess.DeployDeviceName, deployDeviceName); + + var settings = new DebugLaunchSettings(launchOptions) + { + Executable = typeof(CorDebugProcess).Assembly.Location, + Arguments = commandLine, + LaunchOperation = DebugLaunchOperation.CreateProcess, + PortSupplierGuid = DebugPortSupplier.PortSupplierGuid, + PortName = NanoFrameworkPackage.NanoDeviceCommService.Device.Description, + Project = VsHierarchy, + LaunchDebugEngineGuid = CorDebug.EngineGuid + }; + + stopDebugEngine = false; + return new IDebugLaunchSettings[] { settings }; + } + } + finally { - Executable = typeof(CorDebugProcess).Assembly.Location, - Arguments = commandLine, - LaunchOperation = DebugLaunchOperation.CreateProcess, - PortSupplierGuid = DebugPortSupplier.PortSupplierGuid, - PortName = NanoFrameworkPackage.NanoDeviceCommService.Device.Description, - Project = VsHierarchy, - LaunchDebugEngineGuid = CorDebug.EngineGuid - }; - - return new IDebugLaunchSettings[] { settings }; + if (stopDebugEngine) + { + // On success, the debug engine does not have to be stopped, it will be stopped in the CorDebugProcess + // and the global exclusive access is terminated there. + device.DebugEngine?.Stop(); + } + + exclusiveAccess?.Dispose(); + } } #pragma warning disable S112 // OK to use Exception here diff --git a/vs-extension.shared/DeployProvider/DeployProvider.cs b/vs-extension.shared/DeployProvider/DeployProvider.cs index 6f1f336d..d37b03f9 100644 --- a/vs-extension.shared/DeployProvider/DeployProvider.cs +++ b/vs-extension.shared/DeployProvider/DeployProvider.cs @@ -1,16 +1,6 @@ -// -// Copyright (c) .NET Foundation and Contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using ICSharpCode.Decompiler; -using ICSharpCode.Decompiler.CSharp; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Build; -using Microsoft.VisualStudio.Shell; -using nanoFramework.Tools.Debugger; -using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; using System; using System.Collections.Generic; using System.ComponentModel.Composition; @@ -19,6 +9,14 @@ using System.Reflection; using System.Text.RegularExpressions; using System.Threading; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Build; +using Microsoft.VisualStudio.Shell; +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.NFDevice; +using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; using Task = System.Threading.Tasks.Task; namespace nanoFramework.Tools.VisualStudio.Extension @@ -27,6 +25,8 @@ namespace nanoFramework.Tools.VisualStudio.Extension [AppliesTo(NanoCSharpProjectUnconfigured.UniqueCapability)] internal class DeployProvider : IDeployProvider { + private const int ExclusiveAccessTimeout = 3000; + private static ViewModelLocator _viewModelLocator; private static Package _package; @@ -126,6 +126,12 @@ public async Task DeployAsync(CancellationToken cancellationToken, TextWriter ou bool needsToCloseMessageOutput = false; + // Get exclusive access to the device, but don't wait forever + MessageCentre.InternalErrorWriteLine("Try to get exclusive access to the nanoDevice"); + + using var exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(device, ExclusiveAccessTimeout) + ?? throw new DeploymentException($"Couldn't access the device {device.Description}, it is used by another application!"); + try { MessageCentre.InternalErrorWriteLine("Starting debug engine on nanoDevice"); @@ -404,6 +410,8 @@ await Task.Run(async delegate } finally { + device.DebugEngine?.Stop(); + MessageCentre.StopProgressMessage(); } } diff --git a/vs-extension.shared/NanoDeviceCommService/INanoDeviceCommService.cs b/vs-extension.shared/NanoDeviceCommService/INanoDeviceCommService.cs index c9809214..2c86b6c5 100644 --- a/vs-extension.shared/NanoDeviceCommService/INanoDeviceCommService.cs +++ b/vs-extension.shared/NanoDeviceCommService/INanoDeviceCommService.cs @@ -1,15 +1,13 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using nanoFramework.Tools.Debugger; -using nanoFramework.Tools.Debugger.WireProtocol; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.WireProtocol; namespace nanoFramework.Tools.VisualStudio.Extension { @@ -22,7 +20,5 @@ public interface INanoDeviceCommService bool SelectDevice(string description); TaskAwaiter GetAwaiter(); - - bool ConnectTo(string description = null, int timeout = 5000); } } diff --git a/vs-extension.shared/NanoDeviceCommService/NanoDeviceCommService.cs b/vs-extension.shared/NanoDeviceCommService/NanoDeviceCommService.cs index e366e1d1..5ec06a04 100644 --- a/vs-extension.shared/NanoDeviceCommService/NanoDeviceCommService.cs +++ b/vs-extension.shared/NanoDeviceCommService/NanoDeviceCommService.cs @@ -1,14 +1,12 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using nanoFramework.Tools.Debugger; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using nanoFramework.Tools.Debugger; namespace nanoFramework.Tools.VisualStudio.Extension { @@ -84,17 +82,5 @@ public bool SelectDevice(string deviceId = null) return true; } - - public bool ConnectTo(string deviceId = null, int timeout = 5000) - { - if (deviceId == null) - { - return Device.DebugEngine.Connect(timeout, true); - } - else - { - return DebugClient.NanoFrameworkDevices.FirstOrDefault(d => d.Description == deviceId).DebugEngine.Connect(timeout, true); - } - } } } diff --git a/vs-extension.shared/ToolWindow.DeviceExplorer/DeviceExplorerCommand.cs b/vs-extension.shared/ToolWindow.DeviceExplorer/DeviceExplorerCommand.cs index 3ca6e97c..bd0183bb 100644 --- a/vs-extension.shared/ToolWindow.DeviceExplorer/DeviceExplorerCommand.cs +++ b/vs-extension.shared/ToolWindow.DeviceExplorer/DeviceExplorerCommand.cs @@ -1,19 +1,18 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.ComponentModel.Design; +using System.Text; +using System.Windows; using GalaSoft.MvvmLight.Ioc; using GalaSoft.MvvmLight.Messaging; using Microsoft.VisualStudio.Shell; using nanoFramework.Tools.Debugger; using nanoFramework.Tools.Debugger.Extensions; +using nanoFramework.Tools.Debugger.NFDevice; using nanoFramework.Tools.Debugger.WireProtocol; using nanoFramework.Tools.VisualStudio.Extension.ToolWindow.ViewModel; -using System; -using System.ComponentModel.Design; -using System.Text; -using System.Windows; using Task = System.Threading.Tasks.Task; namespace nanoFramework.Tools.VisualStudio.Extension @@ -83,6 +82,9 @@ internal sealed class DeviceExplorerCommand public const int ShowInternalErrorsCommandID = 0x0300; public const int ShowSettingsCommandID = 0x0420; + // Timeout for exclusive access + private const int ExclusiveAccessTimeout = 3000; + INanoDeviceCommService NanoDeviceCommService; OleMenuCommandService MenuCommandService; @@ -301,6 +303,7 @@ await Task.Run(async delegate var descriptionBackup = ViewModelLocator.DeviceExplorer.SelectedDevice.Description; MessageCentre.StartProgressMessage($"Pinging {descriptionBackup}..."); + GlobalExclusiveDeviceAccess exclusiveAccess = null; try { // disable buttons @@ -309,6 +312,14 @@ await Task.Run(async delegate // make sure this device is showing as selected in Device Explorer tree view ViewModelLocator.DeviceExplorer.ForceNanoDeviceSelection(); + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(ViewModelLocator.DeviceExplorer.SelectedDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + MessageCentre.OutputMessage($"Cannot access {descriptionBackup}, another application is using the device."); + return; + } + // check if debugger engine exists if (NanoDeviceCommService.Device.DebugEngine == null) { @@ -348,6 +359,8 @@ await Task.Run(async delegate // disconnect device NanoDeviceCommService.Device?.DebugEngine?.Stop(true); + exclusiveAccess?.Dispose(); + // enable buttons await UpdateDeviceDependentToolbarButtonsAsync(true); @@ -375,6 +388,7 @@ private async void DeviceCapabilitiesCommandHandler(object sender, EventArgs arg await Task.Run(async delegate { + GlobalExclusiveDeviceAccess exclusiveAccess = null; try { // disable buttons @@ -386,6 +400,14 @@ await Task.Run(async delegate // only query device if it's different if (descriptionBackup.GetHashCode() != ViewModelLocator.DeviceExplorer.LastDeviceConnectedHash) { + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(ViewModelLocator.DeviceExplorer.SelectedDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + MessageCentre.OutputMessage($"Cannot access {descriptionBackup}, another application is using the device."); + return; + } + // check if debugger engine exists if (NanoDeviceCommService.Device.DebugEngine == null) { @@ -543,6 +565,8 @@ await Task.Run(async delegate // disconnect device NanoDeviceCommService.Device?.DebugEngine?.Stop(true); + exclusiveAccess?.Dispose(); + // enable buttons await UpdateDeviceDependentToolbarButtonsAsync(true); @@ -574,6 +598,7 @@ private async void DeviceEraseCommandHandler(object sender, EventArgs arguments) await Task.Run(async delegate { + GlobalExclusiveDeviceAccess exclusiveAccess = null; try { // disable buttons @@ -582,6 +607,14 @@ await Task.Run(async delegate // make sure this device is showing as selected in Device Explorer tree view ViewModelLocator.DeviceExplorer.ForceNanoDeviceSelection(); + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(ViewModelLocator.DeviceExplorer.SelectedDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + MessageCentre.OutputMessage($"Cannot access {descriptionBackup}, another application is using the device."); + return; + } + // check if debugger engine exists if (NanoDeviceCommService.Device.DebugEngine == null) { @@ -639,6 +672,8 @@ await Task.Run(async delegate // disconnect device NanoDeviceCommService.Device?.DebugEngine?.Stop(true); + exclusiveAccess?.Dispose(); + // enable buttons await UpdateDeviceDependentToolbarButtonsAsync(true); @@ -665,6 +700,7 @@ private async void NetworkConfigCommandHandler(object sender, EventArgs argument await Task.Run(async delegate { + GlobalExclusiveDeviceAccess exclusiveAccess = null; try { // disable buttons @@ -673,6 +709,17 @@ await Task.Run(async delegate // make sure this device is showing as selected in Device Explorer tree view ViewModelLocator.DeviceExplorer.ForceNanoDeviceSelection(); + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(ViewModelLocator.DeviceExplorer.SelectedDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + _ = MessageBox.Show($"Cannot access {ViewModelLocator.DeviceExplorer.SelectedDevice.Description}, another application is using the device.", + ".NET nanoFramework Device Explorer", + MessageBoxButton.OK, + MessageBoxImage.Error); + return; + } + // check if debugger engine exists if (NanoDeviceCommService.Device.DebugEngine == null) { @@ -761,6 +808,8 @@ await Task.Run(async delegate // disconnect device NanoDeviceCommService.Device?.DebugEngine?.Stop(true); + exclusiveAccess?.Dispose(); + // enable buttons await UpdateDeviceDependentToolbarButtonsAsync(true); @@ -785,6 +834,7 @@ private async void RebootCommandHandler(object sender, EventArgs arguments) await Task.Run(async delegate { + GlobalExclusiveDeviceAccess exclusiveAccess = null; try { // disable buttons @@ -793,6 +843,14 @@ await Task.Run(async delegate // make sure this device is showing as selected in Device Explorer tree view ViewModelLocator.DeviceExplorer.ForceNanoDeviceSelection(); + // Get exclusive access to the device, but don't wait forever + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(ViewModelLocator.DeviceExplorer.SelectedDevice, ExclusiveAccessTimeout); + if (exclusiveAccess is null) + { + MessageCentre.OutputMessage($"Cannot access {ViewModelLocator.DeviceExplorer.SelectedDevice.Description}, another application is using the device."); + return; + } + // check if debugger engine exists if (NanoDeviceCommService.Device.DebugEngine == null) { @@ -881,6 +939,8 @@ await Task.Run(async delegate // disconnect device NanoDeviceCommService.Device?.DebugEngine?.Stop(true); + exclusiveAccess?.Dispose(); + // enable buttons await UpdateDeviceDependentToolbarButtonsAsync(true); }